<?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: Goodnews Azonubi </title>
    <description>The latest articles on DEV Community by Goodnews Azonubi  (@ideategudy).</description>
    <link>https://dev.to/ideategudy</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%2F1410414%2F4b556832-b8e7-4cc0-b055-e8618f34326c.jpg</url>
      <title>DEV Community: Goodnews Azonubi </title>
      <link>https://dev.to/ideategudy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ideategudy"/>
    <language>en</language>
    <item>
      <title>How I Built and Deployed a Production ready MERN Application on AWS using Modular Terraform and Keyless GitHub Actions OIDC 🚀</title>
      <dc:creator>Goodnews Azonubi </dc:creator>
      <pubDate>Fri, 11 Sep 2026 17:15:54 +0000</pubDate>
      <link>https://dev.to/ideategudy/how-i-built-and-deployed-a-full-stack-mern-application-on-aws-using-modular-terraform-and-keyless-3fba</link>
      <guid>https://dev.to/ideategudy/how-i-built-and-deployed-a-full-stack-mern-application-on-aws-using-modular-terraform-and-keyless-3fba</guid>
      <description>&lt;p&gt;Deploying a full-stack web application into production is often one of the most daunting steps for modern developers. You build a complete application locally, but when it comes to hosting it securely on cloud infrastructure with HTTPS, custom domains, and automated deployment pipelines, things can quickly get overwhelming.&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%2Fk9dtzw4tx84z58zkq231.jpeg" 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%2Fk9dtzw4tx84z58zkq231.jpeg" alt="MERN Deployment Architecture" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this post, I will walk you through &lt;strong&gt;how I built and deployed an end-to-end full-stack MERN application ("Little List")&lt;/strong&gt;—from the frontend user interface and Express REST API to provisioning production AWS cloud infrastructure with &lt;strong&gt;Terraform&lt;/strong&gt; and automating deployments with &lt;strong&gt;Keyless GitHub Actions OIDC&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Whether you are a beginner looking to understand full-stack architecture &amp;amp; cloud deployment or an experienced engineer reviewing multi-environment Terraform design, this guide covers the entire end-to-end workflow!&lt;/p&gt;




&lt;h2&gt;
  
  
  📱 What is "Little List"? (Application Overview)
&lt;/h2&gt;

&lt;p&gt;"Little List" is a full-stack personal productivity and management application built from scratch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React SPA Frontend (Vite)&lt;/strong&gt;: Modern, responsive UI with state management, JWT auth flows, interactive diary logging, and custom URL shortener interfaces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js &amp;amp; Express API Backend&lt;/strong&gt;: Modular REST API with secure authentication, refresh tokens, MongoDB integration, and route validation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MongoDB Database&lt;/strong&gt;: Persistent storage for users, diary entries, and shortened link analytics.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📋 Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before diving in, here is what you will need if you want to replicate this deployment architecture:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;AWS Account&lt;/strong&gt;: An active AWS account with permissions to manage EC2, S3, CloudFront, ECR, IAM, and Route 53.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terraform CLI&lt;/strong&gt;: Installed on your machine (&lt;code&gt;&amp;gt;= 1.16.2&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CLI&lt;/strong&gt;: Installed and configured with your credentials (&lt;code&gt;aws configure&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt;: Installed locally for testing container builds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js &amp;amp; npm&lt;/strong&gt;: Installed locally for building the frontend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Account&lt;/strong&gt;: A repository containing your MERN codebase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Domain (Optional)&lt;/strong&gt;: A domain registered on Namecheap, GoDaddy, or Route 53 if you want custom SSL support (e.g., &lt;code&gt;ideategudy.tech&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🏗️ The Cloud Architecture Overview
&lt;/h2&gt;

&lt;p&gt;Before writing code, let us look at the high-level architecture diagram of what we are provisioning on AWS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                               ┌──────────────────────┐
                               │    GitHub Actions    │
                               └──────────┬───────────┘
                                          │
                        ┌─────────────────┴─────────────────┐
                        │                                   │
                  Frontend deploy                      Backend deploy
                        │                                   │
                        ▼                                   ▼
                ┌───────────────┐                    ┌───────────────┐
                │      S3       │                    │      ECR      │
                │ React build   │                    │ Docker image  │
                └───────┬───────┘                    └───────┬───────┘
                        │                                    │
                        │                                    │ docker pull
                        │                                    ▼
                        │                            ┌───────────────┐
                        │                            │      EC2      │
                        │                            │ Express/Docker│
                        │                            └───────┬───────┘
                        │                                    │
                        │                            ┌───────┴───────┐
                        │                            │      ASG      │
                        │                            │  EC2 fleet    │
                        │                            └───────┬───────┘
                        │                                    │
                        ▼                            ┌───────▼───────┐
                ┌───────────────┐                    │      ALB      │
                │  Route 53 DNS │                    │ Load Balancer │
                └───────┬───────┘                    └───────▲───────┘
                        │                                    │
                        ▼                                    │
                ┌───────────────┐                            │
                │  CloudFront   │────────────────────────────┘
                │  CDN (HTTPS)  │ Path-based routing (/api/*)
                └───────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Core Components Explained for Beginners:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS Route 53&lt;/strong&gt;: Highly available DNS service that manages custom domain records (&lt;code&gt;dev.ideategudy.tech&lt;/code&gt;) and routes client requests directly to CloudFront via A-record Aliases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon S3&lt;/strong&gt;: Hosts the static, compiled single-page React frontend (&lt;code&gt;dist/&lt;/code&gt; build files) privately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon CloudFront&lt;/strong&gt;: A global Content Delivery Network (CDN) that serves the React app over HTTPS and acts as a single reverse proxy for both client and backend requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon ECR (Elastic Container Registry)&lt;/strong&gt;: Private Docker image registry to store backend container builds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS ALB (Application Load Balancer)&lt;/strong&gt;: Receives API traffic from CloudFront and balances requests across EC2 instances.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Auto Scaling Group (ASG)&lt;/strong&gt;: Manages an auto-healing fleet of EC2 instances running Canonical Ubuntu 24.04 LTS and Docker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub OIDC (OpenID Connect)&lt;/strong&gt;: Allows GitHub Actions to obtain short-lived security tokens to deploy to AWS &lt;strong&gt;without storing permanent AWS Secret Access Keys in GitHub&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📂 Repository &amp;amp; Project Structure
&lt;/h2&gt;

&lt;p&gt;Here is how the project files are structured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── client/                     # Vite + React SPA Frontend
├── server/                     # Node.js + Express API Backend (Dockerized)
├── infra/                      # Modular Terraform Infrastructure
│   ├── main.tf                 # Root Terraform Module
│   ├── variables.tf            # Root Variables &amp;amp; Configuration Defaults
│   ├── outputs.tf              # Root Terraform Outputs
│   ├── versions.tf             # AWS &amp;amp; Provider Requirements
│   ├── dev.tfvars.example      # Development Environment Settings
│   ├── prod.tfvars.example     # Production Environment Settings
│   └── modules/
│       ├── vpc/                # VPC, Public Subnets (2 AZs), IGW, Route Tables
│       ├── backend/            # ECR, ALB, ASG, Launch Template, EC2 IAM Role
│       ├── frontend/           # S3 Bucket, CloudFront OAC, CDN Distribution, Route53/ACM
│       └── github_oidc/        # GitHub OIDC Identity Provider &amp;amp; IAM Role
└── .github/
    └── workflows/
        └── deploy.yml          # Parallel CI/CD Workflow (Frontend &amp;amp; Backend)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧱 Key Technical Deep Dives
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Unified Single-Domain Routing with CloudFront
&lt;/h3&gt;

&lt;p&gt;One common challenge with SPA + API setups is CORS and handling multiple URLs (&lt;code&gt;api.domain.com&lt;/code&gt; vs &lt;code&gt;domain.com&lt;/code&gt;). We solved this by serving &lt;strong&gt;both frontend and backend under the exact same domain (&lt;code&gt;https://dev.ideategudy.tech&lt;/code&gt;)&lt;/strong&gt; via CloudFront Path-Based Routing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Request Path Pattern&lt;/th&gt;
&lt;th&gt;Origin Target&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/&lt;/code&gt; or &lt;code&gt;/assets/*&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;s3-frontend&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Serves static React SPA files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/api/*&lt;/code&gt;, &lt;code&gt;/s/*&lt;/code&gt;, &lt;code&gt;/healthz&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;alb-api&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Forwards API requests directly to the EC2/Express backend&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Pro-Tip&lt;/strong&gt;: When forwarding requests through CloudFront to an ALB, ensure your CloudFront cache behavior explicitly forwards the &lt;code&gt;Authorization&lt;/code&gt;, &lt;code&gt;Accept&lt;/code&gt;, &lt;code&gt;Content-Type&lt;/code&gt;, &lt;code&gt;Origin&lt;/code&gt;, and &lt;code&gt;Referer&lt;/code&gt; headers! Otherwise, CloudFront strips the &lt;code&gt;Authorization: Bearer &amp;lt;jwt&amp;gt;&lt;/code&gt; header by default, leading to &lt;code&gt;401 Unauthorized&lt;/code&gt; errors on authenticated routes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Zero Secrets in GitHub via AWS OIDC
&lt;/h3&gt;

&lt;p&gt;Instead of creating long-lived IAM user keys (&lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;) and pasting them into GitHub Secrets, we configured an &lt;strong&gt;AWS IAM OIDC Identity Provider&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;GitHub Actions assumes a temporal AWS IAM Role dynamically per pipeline run using OpenID Connect authentication. If your pipeline is compromised, there are no static credentials to leak!&lt;/p&gt;




&lt;h2&gt;
  
  
  🌐 Custom Domain, Subdomain Setup (&lt;code&gt;dev.ideategudy.tech&lt;/code&gt;) &amp;amp; SSL Configuration
&lt;/h2&gt;

&lt;p&gt;Primary infrastructure resources (VPC, EC2, ASG, ALB, ECR, S3) are deployed in &lt;code&gt;eu-north-1&lt;/code&gt; (Stockholm). HTTPS is terminated at the CloudFront CDN level using &lt;strong&gt;AWS Certificate Manager (ACM)&lt;/strong&gt; in &lt;code&gt;us-east-1&lt;/code&gt; (required by CloudFront for global edge distributions).&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Use a Subdomain (&lt;code&gt;dev.ideategudy.tech&lt;/code&gt;)?
&lt;/h3&gt;

&lt;p&gt;Instead of pointing the root/apex domain (&lt;code&gt;ideategudy.tech&lt;/code&gt;) directly to this environment, we configured a dedicated subdomain (&lt;code&gt;dev.ideategudy.tech&lt;/code&gt;) for several key reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Environment Isolation (&lt;code&gt;dev&lt;/code&gt; vs &lt;code&gt;prod&lt;/code&gt;)&lt;/strong&gt;: Using subdomains allows us to run isolated development (&lt;code&gt;dev.ideategudy.tech&lt;/code&gt;) and production (&lt;code&gt;ideategudy.tech&lt;/code&gt; or &lt;code&gt;app.ideategudy.tech&lt;/code&gt;) infrastructure in separate Terraform workspaces without DNS conflicts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subdomain NS Delegation in Namecheap&lt;/strong&gt;: By adding custom &lt;code&gt;NS&lt;/code&gt; records for &lt;code&gt;dev.ideategudy.tech&lt;/code&gt; in Namecheap, we delegate only the &lt;code&gt;dev&lt;/code&gt; subdomain DNS resolution to AWS Route 53 while keeping the parent apex domain (&lt;code&gt;ideategudy.tech&lt;/code&gt;) managed at Namecheap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Environment Testing&lt;/strong&gt;: It enables continuous testing of AWS infrastructure changes (CloudFront, ALB, ASG) on a real HTTPS domain without risking downtime for main production domain traffic.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Connecting Namecheap Subdomain (&lt;code&gt;dev.ideategudy.tech&lt;/code&gt;) to AWS:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;In &lt;code&gt;infra/dev.tfvars&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;   &lt;span class="nx"&gt;domain_name&lt;/span&gt;         &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev.ideategudy.tech"&lt;/span&gt;
   &lt;span class="nx"&gt;create_route53_zone&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;terraform apply --var-file="dev.tfvars"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Copy the 4 output nameservers from Terraform:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;   &lt;span class="nx"&gt;route53_nameservers&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
     &lt;span class="s2"&gt;"ns-1143.awsdns-14.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="s2"&gt;"ns-1698.awsdns-20.co.uk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="s2"&gt;"ns-263.awsdns-32.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="s2"&gt;"ns-847.awsdns-41.net"&lt;/span&gt;
   &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Log into &lt;strong&gt;Namecheap&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Domain List&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Manage &lt;code&gt;ideategudy.tech&lt;/code&gt;&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Advanced DNS&lt;/strong&gt; -&amp;gt; Add 4 &lt;code&gt;NS&lt;/code&gt; Records for Host &lt;code&gt;dev&lt;/code&gt; pointing to the Route 53 Nameservers.&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%2Ffbutlbj8q7szwkojwfhp.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%2Ffbutlbj8q7szwkojwfhp.png" alt="Namecheap Subdomain NS Records Screenshot" width="800" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Figure: Configured NS records in Namecheap delegating &lt;code&gt;dev.ideategudy.tech&lt;/code&gt; to AWS Route 53.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;In minutes, free auto-renewing SSL is active across &lt;code&gt;https://dev.ideategudy.tech&lt;/code&gt;!&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  ⚡ Automated CI/CD Pipeline (&lt;code&gt;deploy.yml&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;When code is pushed to &lt;code&gt;main&lt;/code&gt;, GitHub Actions triggers &lt;code&gt;.github/workflows/deploy.yml&lt;/code&gt; which executes &lt;strong&gt;two parallel jobs&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Note for Beginners&lt;/strong&gt;: You do &lt;strong&gt;not&lt;/strong&gt; need to manually build the React frontend or Docker container on your local computer before pushing. GitHub Actions automatically compiles the React production bundle and builds/pushes the Docker image in the cloud during the pipeline run!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;deploy-frontend&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Builds Vite/React bundle.&lt;/li&gt;
&lt;li&gt;Authenticates keylessly to AWS via OIDC.&lt;/li&gt;
&lt;li&gt;Syncs static assets to the private S3 bucket (&lt;code&gt;aws s3 sync&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Invalidates CloudFront edge cache (&lt;code&gt;aws cloudfront create-invalidation&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;deploy-backend&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Builds Docker image for Express API.&lt;/li&gt;
&lt;li&gt;Authenticates to Amazon ECR via OIDC.&lt;/li&gt;
&lt;li&gt;Pushes new Docker tag to ECR.&lt;/li&gt;
&lt;li&gt;Syncs runtime application secrets into AWS SSM Parameter Store (&lt;code&gt;/mern-deploy/dev/...&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Executes zero-downtime container updates across EC2 ASG instances via AWS SSM &lt;code&gt;RunShellScript&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🚀 Step-by-Step Deployment Instructions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Provision AWS Infrastructure with Terraform
&lt;/h3&gt;



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

&lt;span class="c"&gt;# Copy example variables&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;dev.tfvars.example dev.tfvars

&lt;span class="c"&gt;# Edit values in dev.tfvars (domain_name, github_org, github_repo)&lt;/span&gt;
terraform init
terraform plan &lt;span class="nt"&gt;--var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"dev.tfvars"&lt;/span&gt;
terraform apply &lt;span class="nt"&gt;--var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"dev.tfvars"&lt;/span&gt; &lt;span class="nt"&gt;--auto-approve&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiklc5ldjpzjjiz4pa3se.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%2Fiklc5ldjpzjjiz4pa3se.png" alt="Terraform Apply Output Screenshot" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Figure 1: Successful &lt;code&gt;terraform apply&lt;/code&gt; showing outputs for S3, CloudFront, ECR, and ALB.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 2: Configure GitHub Repository Secrets &amp;amp; Variables
&lt;/h3&gt;

&lt;p&gt;In your GitHub repo under &lt;strong&gt;Settings -&amp;gt; Secrets and variables -&amp;gt; Actions&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;em&gt;Tip: You can retrieve all your Terraform output values anytime by running &lt;code&gt;terraform output&lt;/code&gt; inside the &lt;code&gt;infra/&lt;/code&gt; folder.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4szz18ol0pc30g6ywxun.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%2F4szz18ol0pc30g6ywxun.png" alt="GitHub Repository Actions Secrets and Variables" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Figure 2: GitHub Actions repository variables and secrets configured for OIDC deployment.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Secrets&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MONGODB_URI&lt;/code&gt;: MongoDB connection string&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ACCESS_TOKEN_SECRET&lt;/code&gt;: JWT access secret&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;REFRESH_TOKEN_SECRET&lt;/code&gt;: JWT refresh secret&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Variables&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AWS_ROLE_ARN&lt;/code&gt;: Output &lt;code&gt;github_actions_role_arn&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AWS_REGION&lt;/code&gt;: &lt;code&gt;eu-north-1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ECR_REPOSITORY&lt;/code&gt;: Output &lt;code&gt;ecr_repository_name&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FRONTEND_BUCKET&lt;/code&gt;: Output &lt;code&gt;frontend_bucket&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CLOUDFRONT_DISTRIBUTION_ID&lt;/code&gt;: Output &lt;code&gt;cloudfront_distribution_id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;API_ASG_NAME&lt;/code&gt;: Output &lt;code&gt;api_asg_name&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SSM_PREFIX&lt;/code&gt;: Output &lt;code&gt;ssm_prefix&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CLIENT_URL&lt;/code&gt;: &lt;code&gt;https://dev.ideategudy.tech&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SERVER_URL&lt;/code&gt;: &lt;code&gt;https://dev.ideategudy.tech&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Trigger Automated Build &amp;amp; Deployment
&lt;/h3&gt;

&lt;p&gt;Push your updates to the repository:&lt;br&gt;
&lt;/p&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;"Deploy production architecture to AWS"&lt;/span&gt;
git push origin main
&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%2Fegdx6n1injvgu0mqgbn7.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%2Fegdx6n1injvgu0mqgbn7.png" alt="GitHub Actions Successful CI/CD Pipeline Run" width="800" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Figure 3: Parallel CI/CD execution for &lt;code&gt;deploy-frontend&lt;/code&gt; and &lt;code&gt;deploy-backend&lt;/code&gt; jobs.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🎉 Live Application Demo
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdaumnnglj3hj7r18s4f6.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%2Fdaumnnglj3hj7r18s4f6.png" alt="Live Deployed Application Screenshot" width="799" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Figure 4: The live full-stack application running on &lt;code&gt;https://dev.ideategudy.tech&lt;/code&gt; served securely over CloudFront HTTPS.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ⚡ Key Benefits &amp;amp; Architectural Trade-Offs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🟢 Key Benefits
&lt;/h3&gt;

&lt;p&gt;Choosing this cloud setup over monolithic single-server hosting provides several high-value production advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🚀 Zero-Downtime &amp;amp; Ultra-Fast Global Delivery&lt;/strong&gt;: &lt;br&gt;
By hosting the React SPA on &lt;strong&gt;Amazon S3 + CloudFront&lt;/strong&gt;, static assets are cached globally at edge locations close to users. CloudFront delivers lightning-fast page loads while shielding your backend EC2 instances from static file requests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;💰 Extreme Cost Optimization (Free Tier Friendly)&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;S3 &amp;amp; CloudFront&lt;/strong&gt;: Nearly zero cost for static SPA bandwidth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EC2 Auto Scaling&lt;/strong&gt;: Scales t3.micro instances based on CPU utilization and traffic demands without paying for oversized idle instances.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon ECR Lifecycle Policies&lt;/strong&gt;: Configured to auto-expire old image tags, keeping container storage well under free-tier limits.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🔒 Enterprise-Grade Keyless Security (OIDC)&lt;/strong&gt;:&lt;br&gt;
By avoiding permanent &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt; credentials in GitHub Secrets, your deployment security posture is greatly hardened. GitHub Actions requests temporary, short-lived IAM credentials via OIDC scoped strictly to the resources it needs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🛡️ High Availability &amp;amp; Auto-Healing&lt;/strong&gt;:&lt;br&gt;
The &lt;strong&gt;Application Load Balancer (ALB)&lt;/strong&gt; and &lt;strong&gt;Auto Scaling Group (ASG)&lt;/strong&gt; automatically monitor EC2 instance health. If an instance crashes or fails health checks, ASG automatically terminates it and launches a fresh, containerized replacement instance seamlessly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;📦 Modular &amp;amp; Multi-Environment Ready&lt;/strong&gt;:&lt;br&gt;
The modular Terraform layout (&lt;code&gt;vpc&lt;/code&gt;, &lt;code&gt;backend&lt;/code&gt;, &lt;code&gt;frontend&lt;/code&gt;, &lt;code&gt;github_oidc&lt;/code&gt;) makes it effortless to spin up separate &lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;staging&lt;/code&gt;, and &lt;code&gt;production&lt;/code&gt; environments in minutes with zero manual AWS Console clicking.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  ⚠️ Architectural Trade-Offs &amp;amp; Considerations
&lt;/h3&gt;

&lt;p&gt;No cloud architecture is silver bullet. Understanding the trade-offs helps you decide when to choose this pattern over alternatives:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Initial Setup Complexity vs. Single-Server EC2&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Trade-off&lt;/em&gt;: Setting up S3, CloudFront OAC, ALB, Target Groups, ACM SSL, and OIDC requires more initial Terraform code than launching a single EC2 instance running Docker Compose + Nginx.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Mitigation&lt;/em&gt;: The modular Terraform structure (&lt;code&gt;modules/&lt;/code&gt;) abstracts away this complexity so it only needs to be written once.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cold Start / Provisioning Time for New EC2 Instances&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Trade-off&lt;/em&gt;: When ASG scales up a new instance, the user-data script installs Docker, pulls the image from ECR, and fetches SSM parameters before serving traffic (~2–3 minutes).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Mitigation&lt;/em&gt;: Setting &lt;code&gt;api_min_size = 1&lt;/code&gt; ensures at least one instance is always warm and ready. For even faster scaling, custom pre-built AMIs (via Packer) can be used.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CloudFront Cache Invalidation Lag&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Trade-off&lt;/em&gt;: Updating the React SPA requires invalidating CloudFront caches (&lt;code&gt;/*&lt;/code&gt;), which takes ~30–60 seconds to propagate across all edge locations worldwide.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Mitigation&lt;/em&gt;: Automated in the CI/CD pipeline (&lt;code&gt;aws cloudfront create-invalidation&lt;/code&gt;) so developers don't have to trigger it manually.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stateful Session Management (Sticky Sessions)&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Trade-off&lt;/em&gt;: Because requests are load-balanced across multiple EC2 instances by the ALB, the backend API must remain &lt;strong&gt;stateless&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Mitigation&lt;/em&gt;: Authentication is handled using stateless &lt;strong&gt;JWT tokens&lt;/strong&gt; and external &lt;strong&gt;MongoDB&lt;/strong&gt; storage rather than in-memory server sessions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;Building cloud infrastructure using modular Terraform modules and automated keyless CI/CD pipelines transforms complex AWS operations into predictable, repeatable, and maintainable software engineering workflows.&lt;/p&gt;

&lt;p&gt;By decoupling the architecture into public VPC subnets, S3/CloudFront SPA hosting, and auto-scaling EC2 container fleets, the application stays fast, cost-optimized, and resilient.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Related Documentation &amp;amp; Alternative Architecture
&lt;/h2&gt;

&lt;p&gt;Looking for a simpler, single-server EC2 deployment pattern? Check out the companion article:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://dev.to/ideategudy/how-i-built-and-deployed-a-full-stack-mern-app-on-aws-ec2-using-docker-compose-terraform-and-23k8"&gt;How I Built and Deployed a Full-Stack MERN App on AWS EC2 using Docker Compose, Terraform, and GitHub Actions&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This guide covers deploying the full-stack MERN application on a single AWS EC2 instance using Docker Compose, Nginx reverse proxy, Certbot (Let's Encrypt SSL), and SSH-based GitHub Actions CI/CD.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🤝 Connect &amp;amp; Follow Me
&lt;/h2&gt;

&lt;p&gt;If you found this guide helpful or have any questions about AWS, Terraform, Docker, or MERN stack architecture, let us connect!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🐙 &lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/ideateGudy" rel="noopener noreferrer"&gt;github.com/ideateGudy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💼 &lt;strong&gt;LinkedIn&lt;/strong&gt;: &lt;a href="https://www.linkedin.com/in/ideategudy/" rel="noopener noreferrer"&gt;linkedin.com/in/ideategudy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🗃️ &lt;strong&gt;Source Code&lt;/strong&gt;: &lt;a href="https://github.com/ideateGudy/terraform-docker-compose-ci-cd-mern-aws-deploy" rel="noopener noreferrer"&gt;github.com/ideateGudy/terraform-docker-compose-ci-cd-mern-aws-deploy&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Drop a reaction on Dev.to and feel free to star the GitHub repository! Happy coding!&lt;/em&gt; 🚀&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>terraform</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How I Built and Deployed a Full-Stack MERN App on AWS EC2 using Docker Compose, Terraform, and GitHub Actions 🚀</title>
      <dc:creator>Goodnews Azonubi </dc:creator>
      <pubDate>Fri, 11 Sep 2026 12:53:53 +0000</pubDate>
      <link>https://dev.to/ideategudy/how-i-built-and-deployed-a-full-stack-mern-app-on-aws-ec2-using-docker-compose-terraform-and-23k8</link>
      <guid>https://dev.to/ideategudy/how-i-built-and-deployed-a-full-stack-mern-app-on-aws-ec2-using-docker-compose-terraform-and-23k8</guid>
      <description>&lt;p&gt;Building a full-stack web application from scratch is an exciting journey—from writing clean React components and Express REST APIs to persisting data in MongoDB. However, taking that application from your local machine and deploying it securely to production with HTTPS, custom domains, and automated CI/CD can often feel daunting.&lt;/p&gt;

&lt;p&gt;In this tutorial, I will walk you through &lt;strong&gt;how I built an end-to-end full-stack MERN application&lt;/strong&gt; and &lt;strong&gt;deployed it to AWS EC2&lt;/strong&gt; using &lt;strong&gt;Terraform (Infrastructure as Code)&lt;/strong&gt;, &lt;strong&gt;Docker Compose&lt;/strong&gt;, &lt;strong&gt;Nginx Reverse Proxy&lt;/strong&gt;, &lt;strong&gt;Let's Encrypt (Certbot)&lt;/strong&gt; for free HTTPS, and &lt;strong&gt;GitHub Actions&lt;/strong&gt; for push-to-deploy CI/CD.&lt;/p&gt;

&lt;p&gt;Whether you're a beginner learning full-stack development and DevOps or a developer looking for a reproducible cloud deployment pattern, this guide covers the entire end-to-end workflow! 🛠️&lt;/p&gt;




&lt;h2&gt;
  
  
  📱 What is This Project? (Application Overview)
&lt;/h2&gt;

&lt;p&gt;This is a complete full-stack personal diary &amp;amp; management application built from scratch using the MERN stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React SPA Frontend (client)&lt;/strong&gt;: Built with React &amp;amp; Vite. Features interactive diary entry logging, authentication state management, responsive UI, and custom URL shortener views. It is packaged with an optimized Nginx multi-stage Docker build.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js &amp;amp; Express API Backend (server)&lt;/strong&gt;: Lightweight RESTful API supporting JWT user authentication (Access &amp;amp; Refresh tokens), diary management endpoints, and URL redirection logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MongoDB Database (mongo)&lt;/strong&gt;: Containerized MongoDB instance for persistent storage of users, diary logs, and link analytics.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📋 Prerequisites &amp;amp; Tools Required
&lt;/h2&gt;

&lt;p&gt;Before we begin, ensure you have the following installed and set up on your machine:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://git-scm.com/" rel="noopener noreferrer"&gt;Git&lt;/a&gt;&lt;/strong&gt;: Version control system to manage project code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker &amp;amp; Docker Desktop&lt;/a&gt;&lt;/strong&gt;: To build and test containerized services locally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://aws.amazon.com/cli/" rel="noopener noreferrer"&gt;AWS CLI&lt;/a&gt;&lt;/strong&gt;: Configured with IAM credentials (&lt;code&gt;aws configure&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://developer.hashicorp.com/terraform/downloads" rel="noopener noreferrer"&gt;Terraform CLI&lt;/a&gt;&lt;/strong&gt; (v1.16.2 or higher): To provision AWS infrastructure declaratively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Account&lt;/strong&gt;: An active AWS account with permissions to launch EC2, VPC, Security Groups, and Elastic IPs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Domain Name&lt;/strong&gt;: (Optional but recommended) A domain name pointing to AWS (e.g. from Namecheap, Cloudflare, GoDaddy).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🏛️ High-Level System Architecture
&lt;/h2&gt;

&lt;p&gt;Here is how all the moving parts work together once deployed on AWS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                       ┌────────────────────────────────────────────────────────┐
                       │                   AWS EC2 Instance                     │
                       │                                                        │
                       │    ┌──────────────────────────────────────────────┐    │
                       │    │             Nginx Container (client)         │    │
  User Browser         │    │           Exposes Ports 80 / 443             │    │
┌─────────────┐        │    │                                              │    │
│  HTTP /     ├───────►│    │ - Serves built React SPA frontend            │    │
│  HTTPS      │        │    │ - Reverse proxies /api and /s/ to backend    │    │
└─────────────┘        │    └──────────────────────┬───────────────────────┘    │
                       │                           │                            │
                       │                           ▼                            │
                       │    ┌──────────────────────────────────────────────┐    │
                       │    │         Node.js Express Server (server)      │    │
                       │    │              Internal Port 3000              │    │
                       │    └──────────────────────┬───────────────────────┘    │
                       │                           │                            │
                       │                           ▼                            │
                       │    ┌──────────────────────────────────────────────┐    │
                       │    │               MongoDB (mongo)                │    │
                       │    │            Internal Port 27017               │    │
                       │    └──────────────────────────────────────────────┘    │
                       └────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Architectural Highlights:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nginx (Frontend Container)&lt;/strong&gt;: Listens on standard public ports &lt;code&gt;80&lt;/code&gt; (HTTP) and &lt;code&gt;443&lt;/code&gt; (HTTPS). It serves the production React build and acts as a &lt;strong&gt;reverse proxy&lt;/strong&gt;, forwarding &lt;code&gt;/api/&lt;/code&gt; traffic to the backend server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js Express (Backend Container)&lt;/strong&gt;: Communicates internally over Docker's bridge network (&lt;code&gt;port 3000&lt;/code&gt;). It is &lt;strong&gt;not&lt;/strong&gt; directly exposed to the public internet, adding a layer of security.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MongoDB (Database Container)&lt;/strong&gt;: Persists application state internally (&lt;code&gt;port 27017&lt;/code&gt;) using Docker named volumes.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📁 Repository Structure Overview
&lt;/h2&gt;

&lt;p&gt;Here is the clean layout of our project repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── client/                     # React Frontend Application (Vite + Nginx Dockerfile)
│   ├── Dockerfile              # Multi-stage build (Node build -&amp;gt; Nginx runtime)
│   ├── nginx.conf              # Production Nginx reverse proxy configuration (SSL HTTPS)
│   └── nginx.local.conf        # Local development Nginx configuration (HTTP localhost)
├── server/                     # Node.js Express Backend API
│   └── Dockerfile              # Lightweight Node 22 Alpine container build
├── terraform/                  # Terraform IaC Infrastructure Code
│   ├── main.tf                 # Root Terraform entry point
│   ├── variables.tf            # Variable definitions
│   ├── outputs.tf              # Server Public IP, Domain &amp;amp; SSH output definitions
│   └── modules/ec2/            # EC2 module (Instance, Security Group, EIP, UserData)
├── .github/workflows/
│   └── deploy.yml              # GitHub Actions automated SSH deployment pipeline
├── docker-compose.yml          # Multi-container production orchestration config
├── docker-compose.override.yml.example # Local HTTP development override template
└── .env.example                # Sample environment variables reference
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🚀 Step-by-Step Deployment Walkthrough
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Local Development &amp;amp; Container Verification
&lt;/h3&gt;

&lt;p&gt;First, verify that your multi-container application runs smoothly on your local machine using Docker Compose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Clone the repository&lt;/span&gt;
git clone https://github.com/ideateGudy/deploy-list.git
&lt;span class="nb"&gt;cd &lt;/span&gt;deploy-list

&lt;span class="c"&gt;# 2. Copy sample environment file&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env

&lt;span class="c"&gt;# 3. Enable local HTTP development config (bypasses SSL cert check on localhost)&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;docker-compose.override.yml.example docker-compose.override.yml

&lt;span class="c"&gt;# 4. Build and launch containers locally&lt;/span&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;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;How Local vs Production Works&lt;/strong&gt;: Creating &lt;code&gt;docker-compose.override.yml&lt;/code&gt; tells Docker to use &lt;code&gt;nginx.local.conf&lt;/code&gt; so your app runs on &lt;code&gt;http://localhost&lt;/code&gt; without expecting SSL certificates. Since &lt;code&gt;docker-compose.override.yml&lt;/code&gt; is in &lt;code&gt;.gitignore&lt;/code&gt;, it will &lt;strong&gt;not&lt;/strong&gt; be pushed to EC2. On EC2, Docker Compose automatically falls back to &lt;code&gt;nginx.conf&lt;/code&gt; with full Let's Encrypt HTTPS support!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can test your application locally at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend SPA&lt;/strong&gt;: &lt;code&gt;http://localhost&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend API&lt;/strong&gt;: &lt;code&gt;http://localhost/api/auth/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health Check&lt;/strong&gt;: &lt;code&gt;http://localhost/healthz&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr9ze8jzcdlpurwjsavkh.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%2Fr9ze8jzcdlpurwjsavkh.png" alt="Local Docker Containers Screenshot" width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Figure 1: Docker Desktop showing all three containers (&lt;code&gt;diary-mongo&lt;/code&gt;, &lt;code&gt;diary-server&lt;/code&gt;, and &lt;code&gt;diary-client&lt;/code&gt;) running green and healthy.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh4xyo7mykm3phmzo1zbf.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%2Fh4xyo7mykm3phmzo1zbf.png" alt="Live Local Frontend Application Screenshot" width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Figure 2: The full-stack application running live in browser on &lt;code&gt;http://localhost&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Step 2: Provision Infrastructure using Terraform (IaC)
&lt;/h3&gt;

&lt;p&gt;Instead of manually clicking through the AWS Console, we use &lt;strong&gt;Terraform&lt;/strong&gt; to provision an EC2 instance, an Elastic IP (static IP), Security Groups (firewall rules), and install Docker via automated &lt;code&gt;user_data&lt;/code&gt; scripts.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Configure AWS CLI &amp;amp; Security Key Pair
&lt;/h4&gt;

&lt;p&gt;Make sure your AWS CLI is authenticated and generate an AWS EC2 Key Pair (e.g. &lt;code&gt;ann_notch&lt;/code&gt; or &lt;code&gt;diary-app-key&lt;/code&gt;).&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Provision with Terraform
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Navigate to terraform directory&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;terraform

&lt;span class="c"&gt;# Create your tfvars configuration file&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;terraform.tfvars.example terraform.tfvars
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit your &lt;code&gt;terraform.tfvars&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;aws_region&lt;/span&gt;       &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eu-north-1"&lt;/span&gt;
&lt;span class="nx"&gt;environment&lt;/span&gt;      &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"prod"&lt;/span&gt;
&lt;span class="nx"&gt;instance_type&lt;/span&gt;    &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t3.micro"&lt;/span&gt;
&lt;span class="nx"&gt;key_name&lt;/span&gt;         &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ann_notch"&lt;/span&gt;            &lt;span class="c1"&gt;# Your AWS SSH Key Pair name&lt;/span&gt;
&lt;span class="nx"&gt;allowed_ssh_cidr&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
&lt;span class="nx"&gt;ami_id&lt;/span&gt;           &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ami-0aba19e56f3eaec05"&lt;/span&gt; &lt;span class="c1"&gt;# Ubuntu 26.04 LTS (amd64)&lt;/span&gt;
&lt;span class="nx"&gt;domain_name&lt;/span&gt;      &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ideategudy.tech"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize and apply the Terraform configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
terraform apply &lt;span class="nt"&gt;-auto-approve&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp6bk364466cvg9ovn96o.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%2Fp6bk364466cvg9ovn96o.png" alt="Terraform Apply Terminal Output" width="799" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once complete, Terraform will output your &lt;code&gt;server_public_ip&lt;/code&gt; (Elastic IP) and &lt;code&gt;domain_name&lt;/code&gt;. Keep these outputs handy!&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3: Configure Custom Domain &amp;amp; DNS Records (Optional but Recommended)
&lt;/h3&gt;

&lt;p&gt;To make your application accessible at a real domain (e.g., &lt;code&gt;ideategudy.tech&lt;/code&gt;) and issue SSL certificates:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to your Domain Registrar DNS settings.&lt;/li&gt;
&lt;li&gt;Create an &lt;strong&gt;A Record&lt;/strong&gt;: Name &lt;code&gt;@&lt;/code&gt; $\rightarrow$ Value &lt;code&gt;&amp;lt;YOUR_SERVER_PUBLIC_IP&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create a &lt;strong&gt;CNAME Record&lt;/strong&gt;: Name &lt;code&gt;www&lt;/code&gt; $\rightarrow$ Value &lt;code&gt;ideategudy.tech&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Verify DNS propagation:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   nslookup ideategudy.tech 8.8.8.8
&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%2Fwgonj5il5m0q0v1ol687.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%2Fwgonj5il5m0q0v1ol687.png" alt="DNS Records Configuration Screenshot" width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 4: Configure GitHub Repository Secrets &amp;amp; Variables
&lt;/h3&gt;

&lt;p&gt;To enable seamless, zero-downtime CI/CD deployment on every code push, navigate to your GitHub Repository:&lt;br&gt;
&lt;strong&gt;Settings &amp;gt; Secrets and variables &amp;gt; Actions&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Tip for Beginners&lt;/strong&gt;: You can retrieve all your Terraform output values (like your EC2 Public IP) anytime by running &lt;code&gt;terraform output&lt;/code&gt; inside the &lt;code&gt;terraform/&lt;/code&gt; folder!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Add the following &lt;strong&gt;Repository Secrets&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Secret Name&lt;/th&gt;
&lt;th&gt;Value / Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EC2_HOST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Public Elastic IP address from Terraform output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EC2_USERNAME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ubuntu&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EC2_SSH_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Entire private SSH key contents (&lt;code&gt;.pem&lt;/code&gt; file)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MONGODB_URI&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mongodb://mongo:27017/diarydb&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ACCESS_TOKEN_SECRET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Secure random secret string for JWT access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;REFRESH_TOKEN_SECRET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Secure random secret string for JWT refresh&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Add the following &lt;strong&gt;Repository Variable&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable Name&lt;/th&gt;
&lt;th&gt;Value / Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DOMAIN_NAME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ideategudy.tech&lt;/code&gt; (or your domain name)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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%2Foqr51xz4odo7rbnzy56b.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%2Foqr51xz4odo7rbnzy56b.png" alt="GitHub Actions Secrets &amp;amp; Variables Settings" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Step 5: Automated Deployment via GitHub Actions CI/CD
&lt;/h3&gt;

&lt;p&gt;Our repository includes &lt;code&gt;.github/workflows/deploy.yml&lt;/code&gt;. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Note for Beginners&lt;/strong&gt;: You do &lt;strong&gt;not&lt;/strong&gt; need to manually SSH into your server or build Docker containers on your local computer before pushing! When you push code to GitHub, GitHub Actions automatically connects to your EC2 server, pulls the latest code, issues SSL certificates, and restarts your application containers in the cloud.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you push changes to the &lt;code&gt;main&lt;/code&gt; branch:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GitHub Actions connects securely to your EC2 server via SSH.&lt;/li&gt;
&lt;li&gt;It pulls the latest code.&lt;/li&gt;
&lt;li&gt;It updates environment variables (&lt;code&gt;.env&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;It executes Certbot to issue/renew free Let's Encrypt SSL certificates automatically.&lt;/li&gt;
&lt;li&gt;It runs &lt;code&gt;docker compose up -d --build&lt;/code&gt; to re-deploy updated containers with zero downtime.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To trigger your deployment:&lt;br&gt;
&lt;/p&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;"Deploy fullstack application to production"&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Head over to the &lt;strong&gt;Actions&lt;/strong&gt; tab in GitHub to watch your deployment complete in real-time! ⚡&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%2Fmr1g9w2ma29bhh9rr6zy.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%2Fmr1g9w2ma29bhh9rr6zy.png" alt="GitHub Actions Pipeline Successful Run" width="800" height="263"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 6: Live Production Verification &amp;amp; Secure HTTPS
&lt;/h3&gt;

&lt;p&gt;Once the GitHub Actions workflow completes successfully, open your browser and navigate to your production domain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deployed App (HTTPS)&lt;/strong&gt;: &lt;code&gt;https://ideategudy.tech&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5c4hm0amqypa7f7dlsx0.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%2F5c4hm0amqypa7f7dlsx0.png" alt="Live Application with SSL Certificate Padlock" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  ❓ Troubleshooting &amp;amp; Common Gotchas
&lt;/h3&gt;

&lt;p&gt;If something doesn't work on your first try, don't worry! Here are the most common hiccups beginners face and how to fix them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SSH Connection Timeout in GitHub Actions&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Cause&lt;/em&gt;: Security Group blocking SSH or wrong IP.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Fix&lt;/em&gt;: Ensure &lt;code&gt;allowed_ssh_cidr = "0.0.0.0/0"&lt;/code&gt; in &lt;code&gt;terraform.tfvars&lt;/code&gt; and check that &lt;code&gt;EC2_HOST&lt;/code&gt; secret matches your Terraform output IP.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Certbot / SSL Failure&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Cause&lt;/em&gt;: DNS record has not fully propagated before pushing to GitHub Actions.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Fix&lt;/em&gt;: Run &lt;code&gt;nslookup yourdomain.com&lt;/code&gt; first to confirm your domain resolves to your EC2 IP before triggering the workflow.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MongoDB Connection Failed&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Cause&lt;/em&gt;: Containers starting out of order or invalid URI.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Fix&lt;/em&gt;: Make sure &lt;code&gt;MONGODB_URI&lt;/code&gt; in GitHub Secrets is set to &lt;code&gt;mongodb://mongo:27017/diarydb&lt;/code&gt; (using the container service name &lt;code&gt;mongo&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🛡️ Production Best Practices Implemented
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IMDSv2 Enforced&lt;/strong&gt;: AWS Instance Metadata Service v2 required for enhanced EC2 protection (&lt;code&gt;http_tokens = "required"&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EBS Volume Encryption&lt;/strong&gt;: Server storage volume (&lt;code&gt;gp3&lt;/code&gt;) encrypted by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log Management&lt;/strong&gt;: Docker container logging capped at 10MB per file to prevent disk exhaustion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Security Headers&lt;/strong&gt;: Nginx configured with &lt;code&gt;X-Frame-Options&lt;/code&gt;, &lt;code&gt;X-Content-Type-Options&lt;/code&gt;, and &lt;code&gt;server_tokens off&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚡ Key Benefits of This Architecture
&lt;/h2&gt;

&lt;p&gt;Choosing this containerized single-server deployment architecture provides several high-value advantages for developers and small-to-medium applications:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;💰 Cost Efficiency (AWS Free Tier Friendly)&lt;/strong&gt;:&lt;br&gt;
By running Nginx, Node.js Express, and MongoDB inside a single containerized &lt;code&gt;t3.micro&lt;/code&gt; EC2 instance with an Elastic IP, you eliminate the overhead of paying for managed load balancers (ALBs) or separate database instances (RDS) during initial rollout or MVP stages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;🔒 Enhanced Security &amp;amp; Internal Networking&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Internal Docker Bridge&lt;/strong&gt;: Backend API (&lt;code&gt;port 3000&lt;/code&gt;) and MongoDB (&lt;code&gt;port 27017&lt;/code&gt;) are isolated on internal Docker networks and not exposed directly to the internet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nginx Reverse Proxy&lt;/strong&gt;: Shields application servers by acting as the sole entry point, handling request routing, rate limiting, and SSL termination.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🔁 Clean Dev-to-Prod Parity with Docker Compose&lt;/strong&gt;:&lt;br&gt;
The exact same &lt;code&gt;docker-compose.yml&lt;/code&gt; file used for local development runs in production. This eliminates the classic &lt;em&gt;"it works on my machine"&lt;/em&gt; bugs by ensuring identical runtimes across development and cloud environments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🚀 Automated Reproducibility (Infrastructure as Code)&lt;/strong&gt;:&lt;br&gt;
With &lt;strong&gt;Terraform&lt;/strong&gt;, your entire cloud infrastructure (EC2, VPC, Security Groups, Elastic IP) is defined declaratively as code. Re-creating or destroying your environment requires just a single command (&lt;code&gt;terraform apply&lt;/code&gt; or &lt;code&gt;terraform destroy&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;⚡ Frictionless Push-to-Deploy CI/CD&lt;/strong&gt;:&lt;br&gt;
The &lt;strong&gt;GitHub Actions&lt;/strong&gt; SSH workflow completely automates the release cycle. Every code push automatically updates code, manages SSL certificates via Certbot, and builds updated containers with zero manual server maintenance required.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;Congratulations! 🥳 You've built and deployed a production-ready, containerized full-stack application on AWS!&lt;/p&gt;

&lt;p&gt;Here is a quick recap of what we accomplished:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Orchestrated local multi-container environments using &lt;strong&gt;Docker Compose&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Automated cloud infrastructure provisioning using &lt;strong&gt;Terraform&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Established secure reverse-proxying and routing with &lt;strong&gt;Nginx&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Automated SSL HTTPS certificate generation via &lt;strong&gt;Let's Encrypt &amp;amp; Certbot&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Built a push-to-deploy CI/CD pipeline using &lt;strong&gt;GitHub Actions&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Infrastructure-as-Code and containerization give you complete confidence that your deployment is reproducible, maintainable, and scalable.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Related Documentation &amp;amp; Alternative Architecture
&lt;/h2&gt;

&lt;p&gt;Looking for an enterprise-grade, multi-environment AWS deployment pattern? Check out the companion article:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://dev.to/ideategudy/how-i-built-and-deployed-a-full-stack-mern-application-on-aws-using-modular-terraform-and-keyless-3fba"&gt;How I Built and Deployed a Full-Stack MERN Application on AWS using Modular Terraform and Keyless GitHub Actions OIDC&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This guide covers an advanced deployment architecture featuring S3 static site hosting, CloudFront CDN, Application Load Balancers (ALB), EC2 Auto Scaling Groups (ASG), Amazon ECR, and keyless deployments with GitHub Actions OIDC.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🤝 Connect &amp;amp; Follow Me
&lt;/h2&gt;

&lt;p&gt;If you found this guide helpful, hit the ❤️ button, bookmark it for later, and feel free to connect with me!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🐙 &lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/ideateGudy" rel="noopener noreferrer"&gt;github.com/ideateGudy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💼 &lt;strong&gt;LinkedIn&lt;/strong&gt;: &lt;a href="https://www.linkedin.com/in/ideategudy/" rel="noopener noreferrer"&gt;linkedin.com/in/ideategudy&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Got questions or run into issues? Drop a comment below—I'd love to help out! Happy coding! 🚀&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>docker</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Build Smart AI Agents with Mastra and TypeScript (Integrating with Telex.im)</title>
      <dc:creator>Goodnews Azonubi </dc:creator>
      <pubDate>Sat, 01 Nov 2025 22:15:28 +0000</pubDate>
      <link>https://dev.to/ideategudy/how-to-create-ai-agents-using-mastra-and-typescript-1pai</link>
      <guid>https://dev.to/ideategudy/how-to-create-ai-agents-using-mastra-and-typescript-1pai</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Ever wish there was a smarter way to search for tech jobs? I built an AI agent that understands natural language queries like "Find me 5 latest Flutter jobs" and returns relevant postings from public job feeds.&lt;/p&gt;

&lt;p&gt;This isn't a tutorial on how &lt;em&gt;to build&lt;/em&gt; agents from scratch. Instead, I'm walking you through how &lt;em&gt;this specific project&lt;/em&gt; works, the Mastra features I used, and how I integrated it with &lt;a href="https://telex.im/" rel="noopener noreferrer"&gt;Telex.im&lt;/a&gt; — an AI agent platform like Make (you can also think of it as a Slack alternative for education, communities, or bootcamps).&lt;/p&gt;

&lt;h2&gt;
  
  
  Telex:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Live agent on Telex: &lt;a href="https://telex.im/telex-ai-intergration/colleagues/019a40c8-e960-7cab-94a7-d525e02e6a3b/019a40c8-da57-7ca5-a054-e36f6c09bb3c" rel="noopener noreferrer"&gt;Tech Jobs Agent&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv6xuz4a7qimndspjcd61.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%2Fv6xuz4a7qimndspjcd61.png" alt=" " width="800" height="373"&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%2F2islelxcmclvcrud9z72.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%2F2islelxcmclvcrud9z72.png" alt=" " width="800" height="360"&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%2Fvw2y6mt7wsw972xk9thi.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%2Fvw2y6mt7wsw972xk9thi.png" alt=" " width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The agent workflow is simple but practical:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User asks: "Show me remote backend roles"&lt;/li&gt;
&lt;li&gt;Agent extracts keywords: &lt;code&gt;['backend', 'remote']&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Agent calls a tool that searches cached RSS feeds&lt;/li&gt;
&lt;li&gt;Tool returns ranked, deduplicated jobs&lt;/li&gt;
&lt;li&gt;Agent formats results for the user&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You'll need Node.js 18+, an OpenAI API key, and comfort reading TypeScript. We're not reinventing the wheel here—just building on top of some solid libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Need
&lt;/h2&gt;

&lt;p&gt;Before diving in, have these ready:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js 18+&lt;/strong&gt; and npm&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An OpenAI API key&lt;/strong&gt; (get one at &lt;a href="https://platform.openai.com/" rel="noopener noreferrer"&gt;https://platform.openai.com/&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Basic TypeScript&lt;/strong&gt; (don't need to be expert—just comfortable with types)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've never used Mastra before, that's fine. I'll explain the key pieces as we go.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Big Picture: How It All Fits Together
&lt;/h2&gt;

&lt;p&gt;Imagine you're building a small app with AI. You don't just ask the AI a question and hope for the best. You:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Give it a tool&lt;/strong&gt; it can use (in our case: "search these job feeds")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Give it strict instructions&lt;/strong&gt; (in our case: "always use the tool, don't make stuff up")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache external data&lt;/strong&gt; so you're not hammering RSS feeds every second&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate everything&lt;/strong&gt; with schemas so the code doesn't break&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's the folder structure I'm working with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/mastra/
├── agents/
│   └── jobs-agent.ts       ← The agent + its instructions
├── tools/
│   └── rss-tool.ts         ← The "search jobs" tool
├── workflows/
│   └── jobs-workflow.ts    ← Compose steps that use the agent
├── utils/
│   ├── keyword-extractor.ts   ← Parse "Find 5 Flutter jobs"
│   ├── feed-cache.ts          ← Cache RSS data locally
│   └── feed-scheduler.ts      ← Auto-refresh feeds every 30 min
├── scorers/
│   └── jobs-scorer.ts      ← Grade agent responses
└── index.ts                ← Wire it all together
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't worry about memorizing this. You'll see it in action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Walking Through the Code: Step by Step
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: The Agent Asks a Question
&lt;/h3&gt;

&lt;p&gt;When a user types a query, it goes to the &lt;code&gt;jobsAgent&lt;/code&gt;. Here's what that agent looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jobsAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Jobs Agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Fetches recent remote and tech-related job listings from public RSS feeds&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
You are a jobs search assistant. ALWAYS use the fetch-jobs tool to search for jobs.
Never make up job listings. If the tool returns 0 jobs, clearly state that no matches were found.
Format results with: Title, Company, Location, Description, Posted Date.
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai/gpt-4o-mini&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rssTool&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;memory&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;Memory&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;storage&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;LibSQLStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file:../mastra.db&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;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mastra Features Used
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Agent&lt;/code&gt;: Defines the AI’s role, behavior, and model.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Tool&lt;/code&gt;: Lets the agent call external logic (RSS fetcher).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Memory&lt;/code&gt;: Persistent storage using LibSQLStore.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Scorers&lt;/code&gt;: Evaluate the agent’s performance after each response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Workflows&lt;/code&gt;: Combine multiple steps (keyword extraction → tool call → response).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What's happening here:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We're creating an agent that uses GPT-4o-mini (a fast, cheap OpenAI model)&lt;/li&gt;
&lt;li&gt;We give it &lt;strong&gt;strict instructions&lt;/strong&gt; to always use the &lt;code&gt;fetch-jobs&lt;/code&gt; tool (this prevents hallucination)&lt;/li&gt;
&lt;li&gt;It can access a memory store to remember past conversations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key insight: &lt;strong&gt;the instructions are very explicit&lt;/strong&gt;. We don't say "maybe search for jobs." We say "ALWAYS use the fetch-jobs tool." This keeps the agent honest.&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%2Fi6f1zcctxzf5cmm5s3iz.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%2Fi6f1zcctxzf5cmm5s3iz.png" alt=" " width="799" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Extracting Keywords
&lt;/h3&gt;

&lt;p&gt;Before calling the tool, we need to extract what the user wants. If someone says "Find me 5 latest Flutter jobs," we extract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keywords: &lt;code&gt;['flutter']&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Limit: &lt;code&gt;5&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Location: maybe &lt;code&gt;'remote'&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This happens in &lt;code&gt;keyword-extractor.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extractKeywords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stopwords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;find&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;show&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;latest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;remote&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;job&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jobs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;for&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;me&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;the&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ... 40+ more common words&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stemmer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;natural&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PorterStemmer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^\w\s&lt;/span&gt;&lt;span class="sr"&gt;+.#&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// keep tech symbols like + and #&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;stopwords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;stemmer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  &lt;span class="c1"&gt;// "jobs" → "job", "running" → "run"&lt;/span&gt;

  &lt;span class="k"&gt;return&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;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;words&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;  &lt;span class="c1"&gt;// remove duplicates&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Breaking this down:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We remove "noise words" like "find", "show", "the" (these don't help find jobs)&lt;/li&gt;
&lt;li&gt;We use a stemmer to reduce words to their root (so "Flutter", "flutter", "FLUTTER" all become "flutter")&lt;/li&gt;
&lt;li&gt;We return only the meaningful keywords&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input: "Find 5 latest Flutter jobs"
Output: ['flutter']   // (removed: find, latest, jobs)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is effective because job titles usually contain the tech you're searching for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: The RSS Tool Tool Does the Heavy Lifting
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;rssTool&lt;/code&gt; is where the magic happens. It's a Mastra tool, which means it has:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An input schema (what data it accepts)&lt;/li&gt;
&lt;li&gt;An output schema (what it returns)&lt;/li&gt;
&lt;li&gt;An execute function (what it actually does)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This Mastra tool performs the actual job search from cached RSS feeds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rssTool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch-jobs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;e.g., "Flutter developer"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobListingSchema&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keywords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extractKeywords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Load all cached jobs from RSS feeds&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;allJobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;feedUrl&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;rssFeeds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;feedJobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchFeedWithCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;feedUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;allJobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allJobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;feedJobs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Remove duplicates (same job posted to multiple feeds)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uniqueJobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;deduplicateJobs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allJobs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Filter: only keep jobs that match keywords&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matchedJobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;uniqueJobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;desc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;job&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="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kw&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Sort by relevance (title matches &amp;gt; description matches), then by date&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;matchedJobs&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;aScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;bScore&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;aScore&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="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this does:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Takes your keywords and limit&lt;/li&gt;
&lt;li&gt;Loads all cached job listings (from &lt;code&gt;.cache/jobs&lt;/code&gt;, refreshed every 30 minutes)&lt;/li&gt;
&lt;li&gt;Removes duplicates (a job might be posted to multiple feeds)&lt;/li&gt;
&lt;li&gt;Filters to only jobs matching your keywords&lt;/li&gt;
&lt;li&gt;Ranks by relevance (title matches are more important than description matches)&lt;/li&gt;
&lt;li&gt;Returns the top N results&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice: &lt;strong&gt;we don't fetch live RSS here&lt;/strong&gt;. We use a cache. This is important because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RSS feeds can be slow (5-10 second timeouts)&lt;/li&gt;
&lt;li&gt;We'd hit rate limits if we fetched on every query&lt;/li&gt;
&lt;li&gt;Users expect fast responses&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Scorers in Mastra
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://mastra.ai/docs/scorers/overview" rel="noopener noreferrer"&gt;Scorers are automated tests that evaluate Agents outputs using model-graded, rule-based, and statistical methods.&lt;/a&gt; It analyzes what the agent has produced and determines how relevant, accurate, or useful those outputs are to the agent’s goal or the user’s query. I have created 2 scorers 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%2Fg0artlaej1r8t2bf6uhh.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%2Fg0artlaej1r8t2bf6uhh.png" alt=" " width="800" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Keyword Relevance &amp;amp; Tool Selection:
&lt;/h3&gt;

&lt;p&gt;Evaluates if the agent extracted relevant keywords from user input and selected the correct tool (rssTool) with appropriate parameters (query + limit). Returns 0-1 score.&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%2Fpbqpsrta5pxgcptjrof8.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%2Fpbqpsrta5pxgcptjrof8.png" alt=" " width="800" height="332"&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%2Fntyhdw7gn5ibj07vgbzg.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%2Fntyhdw7gn5ibj07vgbzg.png" alt=" " width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Jobs Response Quality:
&lt;/h3&gt;

&lt;p&gt;Evaluates how relevant, correct, and professionally toned a job-search assistant's response is. Returns a 0-1 score (higher is better).&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%2Fq2w2wj4koq9yk21vhgra.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%2Fq2w2wj4koq9yk21vhgra.png" alt=" " width="800" height="346"&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%2Fw79dtos0ya9bd9sxik33.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%2Fw79dtos0ya9bd9sxik33.png" alt=" " width="799" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Feed Caching and Scheduling
&lt;/h3&gt;

&lt;p&gt;The cache lives in &lt;code&gt;.cache/jobs&lt;/code&gt; and stores raw job listings for ~4 hours. Here's how it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchFeedWithCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;feedUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CachedJob&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Try cache first&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadFromCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;feedUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Cache hit! Return instantly&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Cache miss: fetch live, then save&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;feedUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;parseFeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&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="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;pubDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;published&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;feedUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;

    &lt;span class="c1"&gt;// Save to cache for next time&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveToCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;feedUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed to fetch &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;feedUrl&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;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;  &lt;span class="c1"&gt;// Return empty list, don't crash&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;&lt;strong&gt;Why this matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First request for a feed? We fetch and parse the RSS (takes 2-3 seconds)&lt;/li&gt;
&lt;li&gt;Follow-up requests within 4 hours? We serve from cache (instant)&lt;/li&gt;
&lt;li&gt;After 4 hours? We refresh from live feeds again&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 5: Auto-Refresh with a Scheduler
&lt;/h3&gt;

&lt;p&gt;We don't want stale data. So every 30 minutes, we automatically refresh all feeds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;startFeedScheduler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intervalMinutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isSchedulerActive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cronExpression&lt;/span&gt; &lt;span class="o"&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;intervalMinutes&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="c1"&gt;// */30 = every 30 min&lt;/span&gt;

  &lt;span class="nx"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cronExpression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;refreshAllFeeds&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Refreshed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refreshed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; feeds, Failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;isSchedulerActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses &lt;code&gt;node-cron&lt;/code&gt; to schedule a background job&lt;/li&gt;
&lt;li&gt;Every 30 minutes, it refreshes to see if the 4 hours interval has elapsed so it can fetch new feeds and updates the cache&lt;/li&gt;
&lt;li&gt;If a feed fails, we catch the error and continue (don't crash the app)&lt;/li&gt;
&lt;li&gt;This happens in the background—users don't wait for it&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 6: Validation with Zod
&lt;/h3&gt;

&lt;p&gt;Every tool and workflow uses Zod schemas. This is important because TypeScript types disappear at runtime, but Zod validates at runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jobSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;pubDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jobSearchResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobSchema&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If something returns bad data, Zod will catch it&lt;/li&gt;
&lt;li&gt;The agent can't accidentally pass malformed data downstream&lt;/li&gt;
&lt;li&gt;You get helpful error messages instead of mysterious crashes later&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Actually Running This Thing
&lt;/h2&gt;

&lt;p&gt;Let's get it working locally first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This starts a dev server on &lt;a href="http://localhost:4111/" rel="noopener noreferrer"&gt;http://localhost:4111/&lt;/a&gt; with a web playground. Go there, select &lt;code&gt;jobsAgent&lt;/code&gt; from the dropdown, and try typing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Find 5 latest Flutter jobs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see the agent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Extract "flutter" as the keyword&lt;/li&gt;
&lt;li&gt;Search the cached jobs&lt;/li&gt;
&lt;li&gt;Return formatted results with links&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.amazonaws.com%2Fuploads%2Farticles%2Fos9ufes7daktmzb0rtea.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%2Fos9ufes7daktmzb0rtea.png" alt=" " width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For production, build it:&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 build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates &lt;code&gt;.mastra/output&lt;/code&gt; with all the bundled code, and tells you how to start it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--import&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./.mastra/output/instrumentation.mjs .mastra/output/index.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Environment Setup
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file in the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OPENAI_API_KEY=sk-your-key-here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Everything else has sensible defaults.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Extend This project
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Add a New Tool
&lt;/h3&gt;

&lt;p&gt;Say you want a tool that looks up company data. Here's the pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/mastra/tools/company-lookup.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createTool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mastra/core/tools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;companyLookupTool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lookup-company&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Get details about a company&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;foundingYear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;companyName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Call your API or database&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;foundingYear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A cool company&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;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;Then register it in &lt;code&gt;src/mastra/index.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;companyLookupTool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./tools/company-lookup.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// In the mastra config:&lt;/span&gt;
&lt;span class="nl"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;jobsAgent&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;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rssTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;companyLookupTool&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;// ← Add here&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add a New Agent
&lt;/h3&gt;

&lt;p&gt;Same idea—create the agent, give it strict instructions, register it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/mastra/agents/recruiter-agent.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recruiterAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Recruiter Agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`You help recruiters find candidates. Use the lookup-company and fetch-jobs tools.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai/gpt-4o-mini&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;companyLookupTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rssTool&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ... memory, scorers, etc&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in &lt;code&gt;index.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;jobsAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;recruiterAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// ← Add here&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add Observability
&lt;/h3&gt;

&lt;p&gt;The scorers are already set up to grade agent responses. But you can log what happened:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;jobsAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Find Flutter jobs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Agent response:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Tool called:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toolResults&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Artifacts:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;artifacts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Explicit instructions matter more than model size.&lt;/strong&gt; A cheap model with crystal-clear instructions ("ALWAYS use this tool, NEVER make up jobs") beats a fancy model with vague instructions every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Cache external data aggressively.&lt;/strong&gt; RSS feeds are slow and unreliable. Caching with a 4-hour TTL means users get instant responses after the first request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Zod schemas reduce integration bugs.&lt;/strong&gt; Catching bad data at the tool boundary prevents cascading failures downstream.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Schedulers are better than webhooks.&lt;/strong&gt; Periodic refresh via cron is simpler than trying to monitor feed changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Now that you understand how this works, try:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add more feeds&lt;/strong&gt; to &lt;code&gt;src/mastra/data/rss-feeds.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve keyword extraction&lt;/strong&gt; using embeddings or NER instead of keyword matching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add company metadata&lt;/strong&gt; by looking up each job's company in a database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy to production&lt;/strong&gt; using the build output&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://github.com/ideateGudy/tech-jobs-ai-agent.git" rel="noopener noreferrer"&gt;Github Repo&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Mastra docs: &lt;a href="https://docs.mastra.ai" rel="noopener noreferrer"&gt;https://docs.mastra.ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;@rowanmanning/feed-parser: &lt;a href="https://www.npmjs.com/package/@rowanmanning/feed-parser" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@rowanmanning/feed-parser&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;natural (PorterStemmer): &lt;a href="https://www.npmjs.com/package/natural" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/natural&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Zod validation: &lt;a href="https://zod.dev" rel="noopener noreferrer"&gt;https://zod.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;node-cron: &lt;a href="https://www.npmjs.com/package/node-cron" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/node-cron&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>agents</category>
      <category>node</category>
      <category>typescript</category>
      <category>ai</category>
    </item>
    <item>
      <title>Flutter’s UI Architecture Explained for Beginners: Widgets, Elements, and Render Objects</title>
      <dc:creator>Goodnews Azonubi </dc:creator>
      <pubDate>Tue, 28 Oct 2025 21:04:23 +0000</pubDate>
      <link>https://dev.to/ideategudy/flutter-basics-4l2f</link>
      <guid>https://dev.to/ideategudy/flutter-basics-4l2f</guid>
      <description>&lt;h2&gt;
  
  
  Flutter Basics
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Flutter Basics Overview&lt;/li&gt;
&lt;li&gt;
 Prerequisites

&lt;ul&gt;
&lt;li&gt;What is Flutter?&lt;/li&gt;
&lt;li&gt;Dart&lt;/li&gt;
&lt;li&gt;Declarative UI / Reactive Programming&lt;/li&gt;
&lt;li&gt;Multiplatform Development&lt;/li&gt;
&lt;li&gt;What exactly is State?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
The Widget Tree in Flutter

&lt;ul&gt;
&lt;li&gt;
Key Components of the Widget Tree

&lt;ul&gt;
&lt;li&gt;Main Function&lt;/li&gt;
&lt;li&gt;Cupertino vs Material Widgets&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Flutter Rendering and UI Architecture&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Difference between Stateless &amp;amp; Stateful widgets&lt;/li&gt;
&lt;li&gt;
Stateful Widget Lifecycle

&lt;ul&gt;
&lt;li&gt;How setState() triggers UI rebuilds&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Text Changer App Example&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Flutter Basics Overview
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Prerequisites:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Basic understanding of dart programming language &lt;/li&gt;
&lt;li&gt;Flutter installed on your system &lt;/li&gt;
&lt;li&gt;Android studio and Xcode(for Mac)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What is Flutter?
&lt;/h3&gt;

&lt;p&gt;Flutter is a declarative, reactive &lt;a href="https://opensource.com/resources/what-open-source" rel="noopener noreferrer"&gt;open-source&lt;/a&gt;, multiplatform UI toolkit developed by Google. It allows developers to build natively compiled applications for mobile, web, and desktop from a single codebase. Flutter uses the Dart programming language and provides a rich set of pre-designed widgets to create beautiful and responsive user interfaces.&lt;/p&gt;

&lt;p&gt;Flutter developers often hear terms like everything in Flutter is a widget, and this is because Flutter's UI is built using a hierarchy of widgets called the widget tree. Widgets are the building blocks of a Flutter app's user interface, and they can be combined to create complex UIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dart
&lt;/h3&gt;

&lt;p&gt;Dart is an &lt;a href="https://opensource.com/resources/what-open-source" rel="noopener noreferrer"&gt;open-source&lt;/a&gt; client-optimized programming language developed by Google that compiles to the native machine code of the target platform without runtime overhead leading to fast startup times and predictable performance. It is designed to be easy to learn and use, with a syntax that is similar to other popular programming languages like Java, JavaScript, and C#. Dart is the primary language used for developing Flutter applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Declarative UI / Reactive Programming
&lt;/h3&gt;

&lt;p&gt;Flutter uses a declarative approach: you simply describe what the UI should look like for a given state (like color: blue). The framework then uses reactive programming to automatically react to state changes and rebuild the UI efficiently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiplatform Development
&lt;/h3&gt;

&lt;p&gt;Multiplatform development refers to the ability to create applications that can run on multiple platforms using a single codebase. Flutter enables developers to build applications for iOS, Android, web, and desktop platforms using the same codebase, which significantly reduces development time and effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  What exactly is State?
&lt;/h3&gt;

&lt;p&gt;Now you may wonder what state is. In simple terms, state refers to the data or information that determines the behavior and appearance of a widget at a given time. For example, the state of a button widget may include whether it is enabled or disabled, its color, and its text label.&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%2Fnz3p5v0ep9458vnyjqp0.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%2Fnz3p5v0ep9458vnyjqp0.png" alt=" " width="289" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Flutter, state can be managed in various ways, including using built-in stateful widgets, provider packages, or other state management solutions. The choice of state management technique depends on the complexity of the application and the specific requirements of the project. &lt;/p&gt;




&lt;h2&gt;
  
  
  The Widget Tree in Flutter
&lt;/h2&gt;

&lt;p&gt;Now, as the interface of an application is built using widgets, those widgets are combined to create a widget tree. The widget tree is a hierarchical structure that represents the relationship between different widgets in the application. Each widget in the tree can have its own state, which can be updated and managed independently.&lt;/p&gt;

&lt;p&gt;When the state of a widget changes, Flutter automatically rebuilds the affected parts of the widget tree to reflect the new state. This is done using a process called &lt;code&gt;rebuilding,&lt;/code&gt; which involves creating a new instance of the widget with the updated state and replacing the old instance in the widget tree.&lt;/p&gt;

&lt;p&gt;This process is efficient and allows for smooth and responsive user interfaces, as only the affected parts of the widget tree are rebuilt, rather than the entire tree which facilitates the hot reload feature in Flutter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Components of the Widget Tree
&lt;/h3&gt;

&lt;p&gt;The widget tree is structured in a hierarchical manner, with each widget having a parent-child relationship with other widgets. The root of the tree is typically the &lt;code&gt;MaterialApp&lt;/code&gt; or &lt;code&gt;CupertinoApp&lt;/code&gt; widget, which serves as the entry point for the application. From there, other widgets are added as children of the root widget, forming a tree-like structure.&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%2Fuj2nzy2x2pnksub82o40.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%2Fuj2nzy2x2pnksub82o40.png" alt=" " width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Main Function
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;main&lt;/code&gt; function is the entry point of a Flutter application. It is where the execution of the app begins. In the &lt;code&gt;main&lt;/code&gt; function, we typically call the &lt;code&gt;runApp&lt;/code&gt; function, which takes a widget as an argument and inflates it to fill the screen.&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%2Fvk1poimqqeduv3czx2cw.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%2Fvk1poimqqeduv3czx2cw.png" alt=" " width="800" height="611"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Cupertino vs Material Widgets
&lt;/h4&gt;

&lt;p&gt;Flutter provides two sets of pre-designed widgets: Material widgets and Cupertino widgets. Material widgets are designed to follow the Material Design guidelines developed by Google, while Cupertino widgets are designed to follow the iOS design guidelines developed by Apple. Depending on the target platform, developers can choose to use either set of widgets or a combination of both to create a consistent user experience across different platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flutter Rendering and UI Architecture
&lt;/h3&gt;

&lt;p&gt;Flutter initially renders the UI using skia, a 2D graphics rendering engine. Skia is responsible for drawing the UI elements on the screen, and it provides a high-performance rendering pipeline that allows for smooth animations and transitions. But with the release of Flutter 3.0, Flutter introduced a new rendering engine called Impeller, which is designed to provide even better performance and visual fidelity. Impeller is a next-generation rendering engine that leverages modern graphics APIs like Metal on iOS and Vulkan on Android to deliver high-quality graphics and smooth animations.&lt;/p&gt;

&lt;p&gt;Flutter's rendering and UI architecture is primarily built upon three main trees:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Widget Tree&lt;/strong&gt;: The widget tree is the hierarchical structure of widgets that make up the user interface of a Flutter application. Each widget in the tree represents a specific UI element, such as a button, text field, or image. The widget tree is responsible for defining the layout and appearance of the UI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Element Tree&lt;/strong&gt;: The element tree is a lower-level representation of the widget tree. It consists of &lt;code&gt;Element&lt;/code&gt; objects, which are created for each widget in the widget tree (Like a copy of the widget). The element tree is responsible for managing the lifecycle of widgets, including their creation, updating, and destruction. It also handles the communication between widgets and their associated state.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Render Tree (or RenderObject Tree):&lt;/strong&gt;: The render tree is the final representation of the UI that is used for painting on the screen. It consists of &lt;code&gt;RenderObject&lt;/code&gt; instances, which are responsible for the actual layout and painting of the UI elements. The render tree is built from the element tree and is optimized for performance and efficiency during the rendering process.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In summary,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The Widget is the structural description of the UI (Blueprint). it is the &lt;code&gt;immutable&lt;/code&gt; description of what the UI should look like and provides configuration and hierarchy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Element is the &lt;code&gt;mutable&lt;/code&gt; brain that manages the lifecycle of the widget and holds the state. It acts as a bridge between the widget and the render object. Determines what needs to be updated when the widget's state changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Render Object is the Builder/Artist that is responsible for the actual layout and painting(drawing pixels) of the UI on the screen. It takes care of measuring, positioning, and drawing the visual representation of the widget.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interaction between these three trees is crucial for the efficient rendering of the UI in Flutter. When a widget's state changes, Flutter updates the widget tree, which in turn updates the element tree and the render tree. This process ensures that only the affected parts of the UI are redrawn, leading to smooth and responsive user interfaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Difference between Stateless &amp;amp; Stateful widgets
&lt;/h2&gt;

&lt;p&gt;The widget tree can be broadly categorized into two types of widgets based on their ability to manage state:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stateless Widgets&lt;/strong&gt;: These are widgets that do not maintain any state. Once built, their appearance and behavior are fixed and cannot change. Stateless widgets are typically used for static content that does not require user interaction or dynamic updates.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stateful Widgets&lt;/strong&gt;: These are widgets that can maintain state over time. They can rebuild themselves when their state changes, allowing for dynamic and interactive user interfaces. Stateful widgets are used for content that needs to change in response to user input or other events.&lt;/p&gt;&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.amazonaws.com%2Fuploads%2Farticles%2F18hdofqfc9spchai5wkz.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%2F18hdofqfc9spchai5wkz.png" alt=" " width="780" height="132"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Other kinds of widgets include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inherited Widgets&lt;/strong&gt;: These widgets allow data to be passed down the widget tree efficiently. They are used for sharing data between widgets without the need for explicit passing through constructors. examples include &lt;code&gt;Theme&lt;/code&gt; and &lt;code&gt;MediaQuery&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Stateful Widget Lifecycle (initState, dispose)
&lt;/h2&gt;

&lt;p&gt;Stateful widgets have a lifecycle that consists of several stages, each represented by specific methods that can be overridden to perform actions at different points in the widget's life. The key lifecycle methods of a stateful widget are:&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%2F3c994uf7i40800tbsu2j.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%2F3c994uf7i40800tbsu2j.png" alt=" " width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;createState()&lt;/strong&gt;: This method is called when the stateful widget is first created. It returns an instance of the associated &lt;code&gt;State&lt;/code&gt; class, which holds the mutable state for the widget.
&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%2Fse7x9682wd7q2rjrvbyp.png" alt=" " width="800" height="494"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;initState()&lt;/strong&gt;: This method is called after the stateful widget is created and before the widget is built for the first time. It is used to perform any initializations that are required for the state.
&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%2Fv0pfm2t8y9o9xpiz4007.png" alt=" " width="435" height="229"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;didChangeDependencies()&lt;/strong&gt;: Called immediately after &lt;code&gt;initState()&lt;/code&gt;, but before the widget is built for the first time. It's also called when an InheritedWidget that this State depends on changes. It is often a good place to initialize data that depends on the BuildContext (like Theme.of(context)), as initState() may be too early.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;build()&lt;/strong&gt;: This method is called whenever the widget needs to be rebuilt, either due to a change in state or when the parent widget rebuilds. It returns the widget tree that represents the UI of the stateful widget.
&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%2Frb8cqk17y11lpvfv3ymt.png" alt=" " width="632" height="86"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;setState()&lt;/strong&gt;: (Triggered by you) This method is used to notify the framework that the state of the widget has changed and that it needs to be rebuilt.
&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%2Fzcca5didfabts1du60qa.png" alt=" " width="799" height="377"&gt;
The image above illustrates how calling &lt;code&gt;setState()&lt;/code&gt; leads to the invocation of the &lt;code&gt;build()&lt;/code&gt; method, when the user hits enter after inputing a city name in the TextField. it fetches new weather data and updates the UI accordingly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;didUpdateWidget()&lt;/strong&gt;: This method is called whenever the parent widget rebuilds and passes new configuration data down to the child stateful widget. It allows the state to respond to changes in the widget's properties by passing the updated stateful widget as a parameter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;deactivate()&lt;/strong&gt;: This method is called when the State object is removed from the Element tree, but before it is disposed. This might happen temporarily if the widget is moved to a different part of the tree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;dispose()&lt;/strong&gt;: This is called after deactivate() when the State object is permanently removed from the tree. This is the last method called, and it's essential to use it for cleanup (e.g., canceling timers, unsubscribing from streams, disposing of controllers) to prevent memory leaks.
&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%2F5k51gj19t3vns6sak6q8.png" alt=" " width="798" height="188"&gt;
The image above shows the &lt;code&gt;dispose()&lt;/code&gt; method being called when the &lt;code&gt;StatefulWidget&lt;/code&gt; which is a TextFormField controller is removed from the widget tree after a user submits a form. This ensures that resources are properly released and prevents memory leaks.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  How setState() triggers UI rebuilds
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;setState()&lt;/code&gt; method is a crucial part of the stateful widget lifecycle in Flutter. When you call &lt;code&gt;setState()&lt;/code&gt;, you are indicating to the Flutter framework that the internal state of the widget has changed and that the widget needs to be rebuilt to reflect those changes in the UI.&lt;br&gt;
When &lt;code&gt;setState()&lt;/code&gt; is called, the following sequence of events occurs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The callback function passed to &lt;code&gt;setState()&lt;/code&gt; is executed, allowing you to update the state variables of the widget.&lt;/li&gt;
&lt;li&gt;After the state has been updated, Flutter marks the widget as needing to be rebuilt.&lt;/li&gt;
&lt;li&gt;The framework schedules a rebuild of the widget tree, starting from the stateful widget that called &lt;code&gt;setState()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;During the rebuild process, the &lt;code&gt;build()&lt;/code&gt; method of the stateful widget is called, which constructs a new widget tree based on the updated state.&lt;/li&gt;
&lt;li&gt;The framework compares the new widget tree with the previous one and determines which parts of the UI need to be updated.&lt;/li&gt;
&lt;li&gt;Finally, the framework updates the UI by redrawing only the affected parts of the widget tree, resulting in a smooth and efficient update to the user interface.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Text Changer App Example
&lt;/h2&gt;

&lt;p&gt;Click &lt;a href="https://github.com/ideateGudy/Flutter_Mobile_Development_Series/blob/main/day%201_flutter%20basics/text_changer" rel="noopener noreferrer"&gt;Here to view the Text Changer App Explanation.&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%2Fxesr5996330re6zbya69.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%2Fxesr5996330re6zbya69.png" alt=" " width="784" height="528"&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%2F7eeuyc0rgfhj8iaxpqqo.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%2F7eeuyc0rgfhj8iaxpqqo.png" alt=" " width="800" height="637"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In summary, the three trees in Flutter's rendering architecture work together to create a responsive and efficient UI. The widget tree defines the structure and appearance of the UI, the element tree manages the lifecycle and state of the widgets, and the render tree is responsible for the actual painting of the UI on the screen. Understanding these trees and their interactions is key to building performant Flutter applications.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>flutter</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Dynamic Profile Endpoint (Express/Nodejs/Typescript)</title>
      <dc:creator>Goodnews Azonubi </dc:creator>
      <pubDate>Sat, 18 Oct 2025 14:20:00 +0000</pubDate>
      <link>https://dev.to/ideategudy/dynamic-profile-endpoint-expressnodejstypescript-52c3</link>
      <guid>https://dev.to/ideategudy/dynamic-profile-endpoint-expressnodejstypescript-52c3</guid>
      <description>&lt;p&gt;Here’s a walkthrough of my experience building the “Dynamic Profile Endpoint” (GET /me) for the &lt;a href="https://hng.tech/internship" rel="noopener noreferrer"&gt;HNG Internship&lt;/a&gt;  Stage 0 task — using TypeScript, Express, and integrating with the Cat Facts API.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Objective: Dynamic Profile Endpoint
&lt;/h2&gt;

&lt;p&gt;The primary goal was to create a single endpoint that returns static profile data combined with real-time, dynamic data fetched from a third-party service.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Use TypeScript to have typed safety and clearer code.&lt;/li&gt;
&lt;li&gt;Use Express for the HTTP server.&lt;/li&gt;
&lt;li&gt;Add security (helmet), CORS, logging, rate-limiting.&lt;/li&gt;
&lt;li&gt;Clean separation: routes, controller, middleware, error handler.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fetch a live fact (from &lt;a href="https://catfact.ninja" rel="noopener noreferrer"&gt;cat fact api&lt;/a&gt; when serving the /me endpoint.&lt;/p&gt;

&lt;p&gt;Provide a well‑structured JSON response including status, user info, timestamp, fact.&lt;/p&gt;

&lt;h3&gt;
  
  
  Endpoint Requirements:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Method:&lt;/strong&gt; &lt;code&gt;GET&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL:&lt;/strong&gt; &lt;code&gt;/me&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response:&lt;/strong&gt; A JSON object containing static user details (name, email, stack), timestamp to show current time the request was made and a dynamic field (&lt;code&gt;fact&lt;/code&gt;) fetched from &lt;strong&gt;&lt;a href="https://catfact.ninja" rel="noopener noreferrer"&gt;The Cat Facts API&lt;/a&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Success Status:&lt;/strong&gt; &lt;code&gt;HTTP/1.1 200 OK&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 What I Built
&lt;/h2&gt;

&lt;p&gt;The task required building an HTTP GET &lt;code&gt;/me&lt;/code&gt; endpoint that returns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My profile (name, email, stack)&lt;/li&gt;
&lt;li&gt;A dynamic cat fact (from &lt;a href="https://catfact.ninja" rel="noopener noreferrer"&gt;CatFact Ninja&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;A timestamp of the request&lt;/li&gt;
&lt;li&gt;A consistent JSON response structure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To meet these requirements, I used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt;: for static typing and better developer experience&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Express.js&lt;/strong&gt;: for building the get &lt;code&gt;/me&lt;/code&gt; endpoint&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Axios&lt;/strong&gt;: for HTTP requests to the Cat Facts API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Helmet &amp;amp; CORS&lt;/strong&gt;: to secure HTTP headers and allow cross-origin requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting middleware&lt;/strong&gt;: to prevent abuse&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom error &amp;amp; logging middleware&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── controllers/
│   └── profile.controller.ts     # Business logic for the /me endpoint
├── middlewares/
│   ├── errorHandler.ts           # Global error handling middleware
│   ├── logging.ts                # Logging middleware
│   └── rate-limiting.ts          # Rate limiting middleware
├── routes/
│   └── profile.routes.ts         # Defines the GET /me endpoint
├── types/
│   └── profile.ts                  # TypeScript interfaces and types
├── utils/
│   └── apiError.ts                # Custom reusable error class
├── .env                          # Environment variables
├── index.ts                      # Main application entry point
├── package.json
└── tsconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ⚙️ Setup &amp;amp; Running Locally
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Clone the Repository
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ideateGudy/Dynamic-Profile-Endpoint.git
&lt;span class="nb"&gt;cd &lt;/span&gt;Dynamic-Profile-Endpoint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Install Dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Create .env File
&lt;/h3&gt;

&lt;p&gt;In the root directory, create a .env file and add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3000
&lt;span class="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;development
&lt;span class="nv"&gt;CATFACT_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://catfact.ninja
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Start the Server
&lt;/h3&gt;

&lt;p&gt;Development mode (auto-restart with file changes):&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 dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production build:&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 build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run compiled JS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the server starts, access it via:&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:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🧪 Testing the &lt;code&gt;/me&lt;/code&gt; Endpoint&lt;/p&gt;

&lt;p&gt;Sample Response&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user"&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;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"azonubigoodnews@gmail.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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Goodnews Azonubi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"stack"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Nodejs/Express/Typescript"&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;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-10-18T10:43:33.791Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fact"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A tiger’s stripes are like fingerprints"&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;&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%2Frg4gx724nfx9vv5zu65z.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%2Frg4gx724nfx9vv5zu65z.png" alt="Response for get request to " width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  🔍 Behind the Scenes: How It Works
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;GET /me&lt;/code&gt; Route&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Located in: &lt;code&gt;routes/profile.routes.ts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It calls a service function that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Defines static profile data&lt;/li&gt;
&lt;li&gt;Pulls a cat fact via axios&lt;/li&gt;
&lt;li&gt;Adds an ISO timestamp&lt;/li&gt;
&lt;li&gt;Returns the combined response&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Cat Facts API&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;API used: &lt;a href="https://catfact.ninja/fact" rel="noopener noreferrer"&gt;https://catfact.ninja/fact&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Response format:&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;"fact"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cats sleep for 70% of their lives."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"length"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;38&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;ol&gt;
&lt;li&gt;Middlewares&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;helmet: Secures HTTP headers&lt;/p&gt;

&lt;p&gt;cors: Enables basic cross-origin requests&lt;/p&gt;

&lt;p&gt;rate-limiting: Prevents request abuse&lt;/p&gt;

&lt;p&gt;logging: Logs method, path, response time&lt;/p&gt;

&lt;p&gt;errorHandler: Catches and handles server errors&lt;/p&gt;




&lt;p&gt;📚 What I Learned&lt;/p&gt;

&lt;p&gt;Rate Limiting: Added protection against spamming endpoints using express-rate-limit&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%2Fphgl7xdm30iu768uekdf.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%2Fphgl7xdm30iu768uekdf.png" alt="Too many requests" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Middleware Layers: Execution order matters — I learned to carefully apply middlewares in the correct sequence&lt;/p&gt;

&lt;p&gt;Structured Error Handling: Centralized error catching and response formatting&lt;/p&gt;

&lt;p&gt;Project Structure: Separated routing, controller, and middleware for maintainability&lt;/p&gt;




&lt;h3&gt;
  
  
  🚀 Github repo
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Please refer to the full repository for the complete code&lt;/em&gt;&lt;br&gt;
&lt;a href="https://github.com/ideateGudy/Dynamic-Profile-Endpoint.git" rel="noopener noreferrer"&gt;Dynamic-Profile-Endpoint Github Repo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>express</category>
      <category>node</category>
      <category>typescript</category>
      <category>backend</category>
    </item>
    <item>
      <title>Ansible For Beginners - Part 1</title>
      <dc:creator>Goodnews Azonubi </dc:creator>
      <pubDate>Wed, 02 Oct 2024 18:27:50 +0000</pubDate>
      <link>https://dev.to/ideategudy/ansible-for-beginners-part-1-ag7</link>
      <guid>https://dev.to/ideategudy/ansible-for-beginners-part-1-ag7</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Ansible is an open-source automation tool used for configuration management, application deployment, and orchestration. It allows system administrators and developers to automate repetitive tasks and also manage infrastructures more efficiently. Whether you're managing a few servers or a vast infrastructure, Ansible simplifies operations using human-readable YAML configuration files, also known as playbooks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features of Ansible
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Agentless: Ansible doesn't require any special software or agents to be installed on the machines you're managing. It uses SSH to connect to and control remote machines or servers.&lt;/li&gt;
&lt;li&gt;Idempotent: Ansible ensures that your systems are in a consistent state, applying changes only when necessary.&lt;/li&gt;
&lt;li&gt;Simple, Yet Powerful: It uses a simple YAML syntax in the form of playbooks that are easy to read and write.&lt;/li&gt;
&lt;li&gt;Extensible: Ansible can be extended through custom modules or plugins, making it highly flexible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this guide, we'll explore the basics of Ansible and get you started on your journey toward mastering IT automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before diving into Ansible, ensure you meet the following prerequisites:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Basic Understanding of Command-Line Usage: Familiarity with using the terminal on Linux OS.&lt;/li&gt;
&lt;li&gt;Access to a Control Node (Master node): This is the machine where Ansible will be installed and run (it could be your local machine).&lt;/li&gt;
&lt;li&gt;Managed Nodes (Slave Nodes): Remote servers or virtual machines to manage (e.g. AWS, Azure, GCP instances, or virtual machines running locally). You'll need SSH access to these nodes.&lt;/li&gt;
&lt;li&gt;Ansible Installed: We will cover the installation, but if you already have Ansible installed, you’re good to go.&lt;/li&gt;
&lt;li&gt;Basic Networking Knowledge: Understanding IP addresses, SSH, and network ports is helpful for managing remote systems.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction

&lt;ul&gt;
&lt;li&gt;Key Features of Ansible&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Getting Started with Ansible

&lt;ul&gt;
&lt;li&gt;Installation on Linux (Ubuntu/Debian)&lt;/li&gt;
&lt;li&gt;Ansible Configuration&lt;/li&gt;
&lt;li&gt;Setting Up SSH for Remote Access&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Ansible Inventory File

&lt;ul&gt;
&lt;li&gt;Creating a Simple Inventory File&lt;/li&gt;
&lt;li&gt;Inventory Hosts Grouping and Aliases&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Running Ansible Ad-Hoc Commands

&lt;ul&gt;
&lt;li&gt;What Are Ad-Hoc Commands?&lt;/li&gt;
&lt;li&gt;Example: Ping all servers&lt;/li&gt;
&lt;li&gt;Example: Check disk space&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Ansible Modules

&lt;ul&gt;
&lt;li&gt;Introduction to Ansible Modules&lt;/li&gt;
&lt;li&gt;Commonly Used Modules:
&lt;code&gt;apt&lt;/code&gt;
&lt;code&gt;service&lt;/code&gt;
&lt;code&gt;copy&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Ansible Playbooks

&lt;ul&gt;
&lt;li&gt;What is an Ansible Playbook?&lt;/li&gt;
&lt;li&gt;Structure of a Playbook&lt;/li&gt;
&lt;li&gt;Writing Your First Playbook&lt;/li&gt;
&lt;li&gt;Running a Playbook&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Conclusion

&lt;ul&gt;
&lt;li&gt;Summary of Key Concepts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Getting Started with Ansible
&lt;/h2&gt;

&lt;p&gt;Before getting started with using Ansible you need to install it and configure the environment. Here's the steps to set it up on your local machine which will act as the &lt;code&gt;control node.&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation on Linux (Ubuntu/Debian)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;1. Update your system
&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 apt upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;2. Install Ansible
&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 install ansible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;3. Verify installation
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ansible --version
&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%2Fne9690v6mj9h14zsdckw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fne9690v6mj9h14zsdckw.jpg" alt=" " width="800" height="119"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Ansible Configuration
&lt;/h3&gt;

&lt;p&gt;Ansible primarily uses SSH to manage remote servers. Therefore, you need remote access to the managed nodes for ansible to work effectively without requiring a password but using SSH key-based authentication.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setting Up SSH for Remote Access:
&lt;/h4&gt;

&lt;p&gt;1.. Generate SSH key pair&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh-keygen -t rsa -b 4096 -C "ansible@control-node"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;-t rsa: Specifies the type of key to create (RSA in this case).&lt;/li&gt;
&lt;li&gt;-b 4096: Specifies the number of bits in the key (4096 is a strong size).&lt;/li&gt;
&lt;li&gt;-C "ansible@control-node": Adds a comment to the key for easy identification (you can change this comment to match your setup, e.g., &lt;code&gt;"Ansible control node"&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%2Fum2caie6ds7551dyriwa.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fum2caie6ds7551dyriwa.jpg" alt=" " width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A public and private key is generated (ansible_key and  ansible_key.pub) where &lt;code&gt;ansible_key.pub&lt;/code&gt; is the public key which will be copied to all the managed nodes. It will be copied to the &lt;code&gt;authorized_keys&lt;/code&gt; file of the managed nodes. While the &lt;code&gt;ansible_key&lt;/code&gt;file contains the private key and should not be exposed publicly.&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%2Fb1qs8asev0pimqbaj6pg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb1qs8asev0pimqbaj6pg.jpg" alt=" " width="797" height="107"&gt;&lt;/a&gt;&lt;br&gt;
2.. Copy the public key to the managed nodes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can either copy the public key manually to the managed nodes&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%2Fjyepu17zlsv21vnopmm9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjyepu17zlsv21vnopmm9.jpg" alt=" " width="799" height="121"&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%2Fbjrnmxkmtzlt7r3d1moc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbjrnmxkmtzlt7r3d1moc.jpg" alt=" " width="800" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;or you can execute this command which will copy it automatically. Since you don't have SSH access you might connect using the password of the managed node to copy your public key.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh-copy-id -i ~/.ssh/your_public_key.pub user@managed_node_ip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once SSH is configured, Ansible can manage the remote node.&lt;/p&gt;
&lt;h2&gt;
  
  
  Ansible Inventory File
&lt;/h2&gt;

&lt;p&gt;The inventory file contains a list of hosts that Ansible will manage. You can specify hosts or groups of hosts in this file and if you don't create one ansible will use the inventory file default path &lt;code&gt;/etc/ansible/hosts&lt;/code&gt;. Creating your own inventory file in Ansible is crucial for several reasons, especially in terms of organization, flexibility, and scalability in managing systems.&lt;/p&gt;
&lt;h3&gt;
  
  
  Creating a Simple Inventory File
&lt;/h3&gt;


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

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Group of servers
[webservers]
192.168.1.10
192.168.1.11

# Group of databases
[dbservers]
db1.example.com
db2.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Inventory Hosts Grouping and Aliases
&lt;/h3&gt;

&lt;p&gt;Using the square bracket we can group the target servers into different categories like dbservers, webservers etc. Another way to identify them is using an Alias. We can achieve this by including an alias for each server at the beginning of the line &lt;code&gt;(server1, server2, db1)&lt;/code&gt; and assigning the address of the server to the &lt;code&gt;ansible_host&lt;/code&gt; parameter.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ansible_host is an inventory parameter for specifying the &lt;code&gt;dns hostname&lt;/code&gt; or &lt;code&gt;ip address&lt;/code&gt; of the target server.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[webservers]
server1 ansible_host=192.168.1.101
server2 ansible_host=192.168.1.102

[dbservers]
db1 ansible_host=192.168.1.201
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Running Ansible Ad-Hoc Commands
&lt;/h2&gt;

&lt;p&gt;These are one-off tasks that you can execute without creating a playbook. Once Ansible is installed and your inventory file is set up, you can start running ad-hoc commands.&lt;/p&gt;
&lt;h3&gt;
  
  
  What Are Ad-Hoc Commands?
&lt;/h3&gt;

&lt;p&gt;Ad-hoc commands are quick commands run on the managed nodes. They allow you to perform simple tasks without creating a complete playbook and these commands use the ansible command-line tool.&lt;/p&gt;
&lt;h4&gt;
  
  
  Example: Ping all servers
&lt;/h4&gt;

&lt;p&gt;You can use the ping module to check connectivity to all hosts in your inventory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Since we created our own inventory file we need to explicitly define it in our command using the &lt;code&gt;-i&lt;/code&gt; flag so ansible doesn't try to use the default file in the &lt;code&gt;/etc/ansible/hosts&lt;/code&gt; path.
&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%2Fpwn03aw3lvzxbulippxx.jpg" alt=" " width="800" height="438"&gt;
&lt;/li&gt;
&lt;li&gt;We can configure Ansible to use the inventory file we created by adding an &lt;code&gt;ansible.cfg&lt;/code&gt; file in the current working directory. This does not overwrite the default path but only applies within this directory, as it has higher precedence. &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%2Fxa7nocsqgbaszw1mutft.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxa7nocsqgbaszw1mutft.jpg" alt=" " width="799" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can look up other parameters like the &lt;code&gt;private_key_file&lt;/code&gt; which ansible uses to connect to all your managed nodes. You can view others that can be overridden by viewing the default Ansible configuration file located at: &lt;code&gt;/etc/ansible/ansible.cfg&lt;/code&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%2Fdsrwc602szqpehxo4kge.jpg" alt=" " width="800" height="112"&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%2F91evham8wrijw5xj1kp1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F91evham8wrijw5xj1kp1.jpg" alt=" " width="798" height="155"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ansible all -m ping
&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%2Fc4f9qj9tfoypx6lol8qo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4f9qj9tfoypx6lol8qo.jpg" alt=" " width="800" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Example: Check disk space
&lt;/h4&gt;

&lt;p&gt;To check the disk space on all servers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ansible all -m command -a "df -h"
&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%2F32gtvht2necx7n9mlkkr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F32gtvht2necx7n9mlkkr.jpg" alt=" " width="799" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Ansible Modules
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Introduction to Ansible Modules
&lt;/h3&gt;

&lt;p&gt;Ansible modules are reusable units of code that can be used to perform specific tasks on managed nodes. Modules allow you to automate actions such as installing packages, managing services, copying files, and much more. Ansible modules are categorized into various groups based on their functionality&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1. Core Modules:
Core modules are the most essential and widely used modules that ship with Ansible. These are stable and maintained as part of the Ansible core.

&lt;ul&gt;
&lt;li&gt;File Modules: Manage files and directories.
Examples: file, copy, template, fetch, synchronize&lt;/li&gt;
&lt;li&gt;Package Management Modules: Install, update, and remove packages.
Examples: apt, yum, dnf, pip &lt;/li&gt;
&lt;li&gt;and more...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2. Cloud Modules:
These modules allow you to manage cloud infrastructure resources such as virtual machines, storage, networks, and other services from cloud providers.

&lt;ul&gt;
&lt;li&gt;AWS Modules: Manage resources in Amazon Web Services.
Examples: ec2, s3, rds, cloudformation&lt;/li&gt;
&lt;li&gt;and more...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3. Utility Modules:
Utility modules are for general-purpose tasks such as managing files, running commands, or handling notifications.

&lt;ul&gt;
&lt;li&gt;Command and Shell Modules: Run commands or scripts on remote systems.
Examples: command, shell, raw&lt;/li&gt;
&lt;li&gt;and more..&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will be using some of these modules in the next section..&lt;/p&gt;

&lt;h2&gt;
  
  
  Ansible Playbooks
&lt;/h2&gt;

&lt;p&gt;Ansible Playbooks allow you to automate the configuration and deployment of applications on multiple servers in a predictable manner.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is an Ansible Playbook?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A playbook contains one or more "plays," which map a group of hosts to tasks that should be run on those hosts. It is written in the YAML syntax format.&lt;/li&gt;
&lt;li&gt;Playbooks can include variables, conditionals, loops, and more, allowing for complex orchestration.&lt;/li&gt;
&lt;li&gt;Play: This define a set of activities to be run on hosts (task). Each play is a list of dictionary ( in YAML term) separated by a dash &lt;code&gt;-&lt;/code&gt; which contains properties like &lt;code&gt;name, hosts ans tasks.&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Task: This is a single action to be performed on a host or hosts e.g., install a package&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Writing Your First Playbook
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;1. Create a new YAML file (e.g. &lt;code&gt;install_apache.yml&lt;/code&gt; or &lt;code&gt;install_apache.yaml&lt;/code&gt;).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nano install_apache.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A basic ansible playbook structure 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;---
- name: Install and start Apache Web Server
  hosts: webservers
  become: yes  # This enables privilege escalation (sudo)
  tasks:
    - name: Update Package
      apt:
        update_cache: yes  # Ensures the apt cache is updated before installation

    - name: Install Apache
      apt:
        name: apache2
        state: present

    - name: Start Apache service
      service:
        name: apache2
        state: started
&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%2Fsjl2mimvmtemyed53ihr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjl2mimvmtemyed53ihr.jpg" alt=" " width="800" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2. Run your playbook:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ansible-playbook install_apache.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Managed Node: apache is installed on the managed node which has an IP address: &lt;code&gt;192.168.56.10&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%2Fh5718cmn04rhcj4xg88o.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh5718cmn04rhcj4xg88o.jpg" alt=" " width="800" height="155"&gt;&lt;/a&gt;&lt;br&gt;
Default Page: Paste the IP address on your browser to view Apache default page&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%2Fa8006hvwan3uq1kajmk7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa8006hvwan3uq1kajmk7.jpg" alt=" " width="798" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;In this first part of the Ansible for Beginners series, we've covered the following key concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ansible and its primary functions&lt;/li&gt;
&lt;li&gt;Installation and configuration&lt;/li&gt;
&lt;li&gt;Setting up SSH for remote access&lt;/li&gt;
&lt;li&gt;Understanding the inventory file&lt;/li&gt;
&lt;li&gt;Running ad-hoc commands&lt;/li&gt;
&lt;li&gt;Ansible modules and their usage&lt;/li&gt;
&lt;li&gt;Introduction to Ansible Playbooks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What’s Next in Part 2:
&lt;/h3&gt;

&lt;p&gt;In Part 2, we will delve deeper into more advanced topics, including Ansible variables, conditionals, loops, roles and more..&lt;/p&gt;

&lt;p&gt;You can also check out my article on &lt;a href="https://dev.to/ideategudy/protecting-sensitive-data-using-ansible-vault-5h7k"&gt;Protecting Sensitive Data using Ansible Vault&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's connect on LinkedIn &lt;a href="https://www.linkedin.com/in/ideategudy" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ansible</category>
      <category>devops</category>
      <category>cloudcomputing</category>
      <category>automaton</category>
    </item>
    <item>
      <title>Docker For Beginners</title>
      <dc:creator>Goodnews Azonubi </dc:creator>
      <pubDate>Fri, 27 Sep 2024 17:03:05 +0000</pubDate>
      <link>https://dev.to/ideategudy/docker-for-beginners-3jgd</link>
      <guid>https://dev.to/ideategudy/docker-for-beginners-3jgd</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Docker is a powerful tool in today’s software world which helps developers/software engineers package their applications and all the things they need to run smoothly into small containers. These containers work the same no matter where they are used whether on a developer’s laptop or in a big server. This guide is for anyone who wants to learn Docker, from people starting in DevOps and software development to students and anyone curious about technology. We’ll cover the basics of Docker, how to set it up, and how to use it in real-life situations. By the end, you’ll have a good grasp of Docker and how it can make your work easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisite
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Basic understanding of the command line interface (CLI) or terminal.&lt;/li&gt;
&lt;li&gt;Access to a computer with internet connection.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;What is Docker?&lt;/li&gt;
&lt;li&gt;Benefits of using Docker&lt;/li&gt;
&lt;li&gt;Setting Up Docker&lt;/li&gt;
&lt;li&gt;Docker Architecture&lt;/li&gt;
&lt;li&gt;Creating Your First Docker Image&lt;/li&gt;
&lt;li&gt;Basic Docker Networking&lt;/li&gt;
&lt;li&gt;Push Your Image To Docker Hub&lt;/li&gt;
&lt;li&gt;Docker Compose&lt;/li&gt;
&lt;li&gt;Persisting Data with Docker Volumes&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is Docker
&lt;/h2&gt;

&lt;p&gt;Docker is an open-source platform designed to automate the deployment, scaling and management of applications encapsulated into containers. This means that Docker packages the application code along with its dependencies, libraries, configuration files, and other required components into a single, isolated container ensuring that it runs consistently across different computing environments. The underline host where docker is installed is called &lt;code&gt;docker host&lt;/code&gt; or &lt;code&gt;docker engine&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Containers vs. Virtual Machines&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unlike virtual machines, which run on hypervisors and require a full operating system to function, containers share the host OS kernel and isolate processes at the application layer, making them more lightweight and efficient. This also means they can be provisioned faster.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.netsolutions.com/insights/wp-content/uploads/2023/05/Containers-vs-Virtual-Machines.webp" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0rslqdaog1b2d37pyv1g.jpg" alt="Container vs Virtual Machine" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Container vs Container Image
&lt;/h3&gt;

&lt;p&gt;A container is a runnable instance of an image which means containers are created from images. When a container is started, docker takes the image and adds a read-write layer on top allowing it to perform certain tasks like save data and interact with the outside world.&lt;/p&gt;

&lt;p&gt;A simple command like &lt;code&gt;docker run &amp;lt;image name&amp;gt;&lt;/code&gt; can be used to run a container from an image and multiple instances of an image can be run on the same server.&lt;/p&gt;

&lt;p&gt;A container image is the template, while a container is an instance of that template in action.&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%2Fps7e39oggwtrfjkddqwg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fps7e39oggwtrfjkddqwg.jpg" alt=" " width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Docker is used for microservices architectures, continuous integration/continuous deployment (CI/CD), simplifying environment setup and scaling applications. It can also be used for Monolithic Architecture where multiple instances of your image are spin up and you can either scale up (adding more instances — vertical scaling) or scale out (provision new server with same image — horizontal scaling) ensuring consistency across servers.&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%2Fqbafrr875caqhnwxfqvi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqbafrr875caqhnwxfqvi.jpg" alt=" " width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of using Docker
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Efficiency: Containers are lightweight and faster to spin up.&lt;/li&gt;
&lt;li&gt;Portability: Containers can run on any system that supports Docker.&lt;/li&gt;
&lt;li&gt;Isolation: Containers isolate applications from one another and the host system, improving security.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setting Up Docker
&lt;/h2&gt;

&lt;p&gt;Docker can be installed on your OS by downloading it from their official website.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/desktop/install/mac-install/" rel="noopener noreferrer"&gt;Mac&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/desktop/install/windows-install/" rel="noopener noreferrer"&gt;Windows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/engine/install/ubuntu/" rel="noopener noreferrer"&gt;Linux(ubuntu)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/engine/install/centos/" rel="noopener noreferrer"&gt;Linux(CentOs)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you’re done installing Docker, you can test your installation by running a simple docker image (hello-world)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The docker run command does two things here. If it does not find the image in your local system it goes ahead to first run the &lt;code&gt;docker pull &amp;lt;image-name&amp;gt;&lt;/code&gt; which downloads the image from the official &lt;em&gt;dockerhub repository&lt;/em&gt; before it executes the &lt;code&gt;docker run &amp;lt;image-name&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Docker Commands
&lt;/h2&gt;

&lt;p&gt;You might need to add sudo (superuser do) for elevated privileges in a Linux based distribution. sudo docker pull &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;docker pull:&lt;/strong&gt; This command is similar to the git pull command, it simply pulls the docker image from the dockerhub registry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;docker run:&lt;/strong&gt; Run a container from an image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;docker version:&lt;/strong&gt; Check the installed version of docker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;docker ps:&lt;/strong&gt; Lists running containers&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can add the &lt;code&gt;- a&lt;/code&gt; flag to view stopped containers.
docker images: Lists all images (or docker image ls)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;docker stop:&lt;/strong&gt; Stops a running container&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;docker rm:&lt;/strong&gt; delete container&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;docker rmi:&lt;/strong&gt; delete image&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker Architecture
&lt;/h2&gt;

&lt;p&gt;Docker uses a client-server architecture where the docker client talks to the docker daemon. The docker daemon does all the heavy lifting of running, building and distributing your docker containers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Components of Docker
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dockerhub:&lt;/strong&gt; Docker Hub is a cloud-based registry service provided by Docker that allows you to find, store, and share container images.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker Compose:&lt;/strong&gt; Docker Compose is a tool for defining and running multi-container Docker applications. With Docker Compose, you use a YAML file to configure your application’s services, networks, and volumes. With a simple command you can create and start all the services from your configuration file. We will look more on this later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker Daemon:&lt;/strong&gt; This is a background process that manages docker images, containers, networks, and storage volumes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker Engine:&lt;/strong&gt; This is a core component of docker that creates and runs containers.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Docker Objects
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Docker Image:&lt;/strong&gt; A read-only template to create containers which contains all necessary codes and dependencies required to run your application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker Container:&lt;/strong&gt; A runnable instance of an Image. Containers run the actual application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker Volume:&lt;/strong&gt; This is a persistent data storage mechanism used to by containers to store data outside the container’s filesystem. More on this later…&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker Network:&lt;/strong&gt; This is a networking component that allows docker containers to communicate with each other and non-docker environments.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Creating Your First Docker Image
&lt;/h2&gt;

&lt;p&gt;Docker uses Dockerfile for building docker images. A Dockerfile is a text file that contains instructions on how to build a particular application into a docker image and these instructions are processed by the Docker Engine. A simple Dockerfile will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM ubuntu:20.04
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y &amp;lt;dependencies&amp;gt;
COPY . /app
WORKDIR /app
CMD ["./your_app_executable"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;1) FROM ubuntu:20.04&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FROM&lt;/code&gt; is a Dockerfile instruction that specifies the base image for the Docker container. The base image is the starting point for building your own image.&lt;/li&gt;
&lt;li&gt;If the image is not already on your local system docker will pull this image from dockerhub (ubuntu:20.04)&lt;/li&gt;
&lt;li&gt;Tags are a way to version and label docker images making it easier to identify and manage different versions of it. You can always find the tag after the colon (:) which is &lt;code&gt;:20.04&lt;/code&gt; or &lt;code&gt;:v1.0&lt;/code&gt;, &lt;code&gt;:v2.0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2) RUN apt-get update &amp;amp;&amp;amp; apt-get install -y &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;RUN&lt;/code&gt; is a Dockerfile instruction used to execute commands in the container during the build process.&lt;/li&gt;
&lt;li&gt;This line runs commands to update the package list and install necessary dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3) COPY . /app&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;COPY&lt;/code&gt; is a Dockerfile instruction used to copy files and directories from the host filesystem into the container’s filesystem&lt;/li&gt;
&lt;li&gt;This line is copying files from the current working directory on the host machine (.) to the (/app) in the container.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4) WORKDIR /app&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WORKDIR&lt;/code&gt; is a Dockerfile instruction that sets the working directory in the container for any subsequent instructions in the Dockerfile&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;5) CMD ["./your_app_executable"]&lt;/p&gt;

&lt;h2&gt;
  
  
  - &lt;code&gt;CMD&lt;/code&gt; is a Dockerfile instruction used to specify the default command to run when the container starts. These defaults include the executable to run.
&lt;/h2&gt;

&lt;p&gt;To build your image run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t myimage-name .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-t&lt;/code&gt; flag is used to assign a name and an optional tag to the Docker image that is being built from a Dockerfile.&lt;/p&gt;

&lt;p&gt;To run a container from that image run this command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -d --name mycontainer myimage-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added some extra parameters to this command here&lt;/p&gt;

&lt;p&gt;&lt;code&gt;- d&lt;/code&gt;: runs the container in detached mode. In simple terms running the container in the background&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-- name&lt;/code&gt;: the flag is used to assign a unique name to your container for easy identification. You can give it any name depending on it’s function.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Basic Docker Networking
&lt;/h2&gt;

&lt;p&gt;Docker networking allows containers to communicate with each other and with external systems. It is an essential aspect of deploying applications using Docker. To see all the networks on your machine run this command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker network ls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Network Drivers:&lt;/strong&gt; Docker provides several network drivers for different use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;bridge:&lt;/strong&gt; The default driver for standalone containers. Containers on the same bridge network can communicate with each other using their IP address. docker run ubuntu&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;host:&lt;/strong&gt; Removes network isolation between the container and the docker host, using the host’s networking directly. There’s no need for port mapping as the port created on the container can be accessible directly on the host. docker run ubuntu --network=host&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;none:&lt;/strong&gt; Disables all networking. Containers only have a loopback interface. docker run ubuntu --network=none&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Creating a Network:
&lt;/h4&gt;

&lt;p&gt;You can create a user-defined bridge network to allow better control over container communication using the &lt;code&gt;create&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker network create my_bridge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Connecting Containers to a Network:
&lt;/h4&gt;

&lt;p&gt;When starting a container, you can connect it to a specific network using the &lt;code&gt;--network&lt;/code&gt; flag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -d --name my_container --network my_bridge my_image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Inspecting Networks:
&lt;/h4&gt;

&lt;p&gt;To inspect a network and see the connected containers use the &lt;code&gt;inspect&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker network inspect my_bridge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  DNS Resolution:
&lt;/h4&gt;

&lt;p&gt;Docker networks have built-in DNS resolution, allowing containers to resolve each other by name.&lt;/p&gt;

&lt;h3&gt;
  
  
  Push Your Image To Docker Hub
&lt;/h3&gt;

&lt;p&gt;You can push your image to your own docker repository after creating an account on dockerhub. Here is a simple step on how to push your image to your dockerhub repository:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create a Docker Hub Account:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you don’t have a Docker Hub account, you can create one at &lt;a href="https://hub.docker.com/" rel="noopener noreferrer"&gt;dockerhub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Login to dockerhub from the CLI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open your terminal and log in to &lt;a href="https://hub.docker.com/" rel="noopener noreferrer"&gt;dockerhub&lt;/a&gt; using the following command:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;You will be prompted to enter your &lt;a href="https://hub.docker.com/" rel="noopener noreferrer"&gt;dockerhub&lt;/a&gt; username and password.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Tagging Your Image&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before pushing your image to &lt;a href="https://hub.docker.com/" rel="noopener noreferrer"&gt;dockerhub&lt;/a&gt;, you need to tag it with a repository name that includes your &lt;a href="https://hub.docker.com/" rel="noopener noreferrer"&gt;dockerhub&lt;/a&gt; username. Let’s assume your local image is named &lt;code&gt;my-image&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker tag my-image:latest yourdockerhubusername/my-image:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;yourdockerhubusername&lt;/code&gt; with your actual dockerhub username.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Push the Image to Docker Hub&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use the &lt;code&gt;docker push&lt;/code&gt; command to push the tagged image to dockerhub.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker push yourdockerhubusername/my-image:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Docker Compose
&lt;/h3&gt;

&lt;p&gt;Docker Compose allows you to define and run multi-container Docker applications using a YAML file.&lt;/p&gt;

&lt;h4&gt;
  
  
  Writing a Docker Compose File:
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://www.redhat.com/en/topics/automation/what-is-yaml#:~:text=YAML%20is%20a%20human%2Dreadable,is%20for%20data%2C%20not%20documents." rel="noopener noreferrer"&gt;YAML is a human-readable data serialization language that is often used for writing configuration files. Depending on whom you ask, YAML stands for yet another markup language or YAML ain’t markup language (a recursive acronym), which emphasizes that YAML is for data, not documents.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unlike JSON that uses curly braces YAML uses indentation to define blocks of instructions. A YAML file can either have &lt;code&gt;.yaml&lt;/code&gt; or &lt;code&gt;.yml&lt;/code&gt; extension.&lt;/p&gt;

&lt;p&gt;If you’re unfamiliar with the YAML syntax check out this &lt;a href="https://youtu.be/1uFVr15xDGg?si=HzeYhct3qGV2ccNz" rel="noopener noreferrer"&gt;YouTube video&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example of a &lt;code&gt;docker-compose.yml&lt;/code&gt; (this is the standard way of writing a compose file) file for a web application with a database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3.8'

services:
  web:
    build: ./frontend
    ports:
      - "8080:80"

  api:
    build: ./backend
    ports:
      - "3000:3000"
    environment:
      DB_URL: mongodb://db/db-name


  db:
    image: mongodb:4.0-xenial
    ports:
      - "27017:27017"
    volumes:
      - db_data:/data/db

volumes:
  db_data:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Define docker compose version&lt;/li&gt;
&lt;li&gt;Define services: In our case we have frontend(web), backend(api) and database(db).&lt;/li&gt;
&lt;li&gt;We need to tell docker how to build an image for each service. We can either use the &lt;code&gt;build&lt;/code&gt; property and tell docker where to find our Dockerfile which will be in the root folder of each service or we can pull an image from dockerhub using the &lt;code&gt;image&lt;/code&gt; property.&lt;/li&gt;
&lt;li&gt;Next we have to define port mapping for each service using the ports property.&lt;/li&gt;
&lt;li&gt;Setting up an environment variable for the api service to tell where to find the database. We can use the environment property for this. Since all services are connected using the bridge driver we can access each service directly by calling their name &lt;code&gt;(web,api,db)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;volumes&lt;/code&gt; property can be used to store persistent data outside the container’s filesystem.&lt;/li&gt;
&lt;li&gt;In the last line we need to define the volumes first before we can use it (db_data).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Running Multi-Container Applications:
&lt;/h3&gt;

&lt;p&gt;To start services run this command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose up 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once our services are up we can access them using the ports we mapped to each one. Our application has three hosts/containers that communicate with each other using a bridge driver.&lt;/p&gt;




&lt;p&gt;You can test to confirm containers can communicate with each other by running a simple ping command.&lt;/p&gt;

&lt;p&gt;First let’s enter the terminal shell of our frontend(web) container&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker exec -it -u root web_container_id sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have a new command and options here. Let me break it down.&lt;/p&gt;

&lt;p&gt;The command &lt;code&gt;docker exec -it -u root web_container_id sh&lt;/code&gt; is used to run a shell (sh) inside a running Docker container.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exec: Command to run a command in a running container.&lt;/li&gt;
&lt;li&gt;-i: Keeps STDIN open, allowing you to interact with the container. You would be able to provide input to the shell, but the session would not have terminal features, making it less user-friendly.&lt;/li&gt;
&lt;li&gt;-t: Allocates a pseudo-TTY (terminal). You would have a terminal interface, but you wouldn’t be able to provide interactive input.&lt;/li&gt;
&lt;li&gt;-u root: Specifies the user as root to run the command.&lt;/li&gt;
&lt;li&gt;container_id: The ID or name of the running container.&lt;/li&gt;
&lt;li&gt;sh: The shell to run inside the container (Bourne shell).
Now let’s ping the backend (API) service
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;/div&gt;



&lt;p&gt;To stop and remove services run this command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose down 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Docker Volumes and Persistent Storage
&lt;/h3&gt;

&lt;p&gt;Docker volumes are used to persist data generated and used by Docker containers. When you stop a docker container by default without explicitly specifying volume mapping, everything generated and stored in it is deleted making it ephemeral, meaning it is tied to the lifecycle of the container&lt;/p&gt;

&lt;h4&gt;
  
  
  Types of Volumes:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Named Volumes: This is managed by docker and stored in docker’s storage area.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bind Mounts: This maps a file or directory on the host to a container. This offers more control on the data but less portability.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Creating and Using Volumes:
&lt;/h4&gt;

&lt;p&gt;Create a named volume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker volume create my_volume
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a volume when running a container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -d --name my_container -v my_volume:/data image-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;my_volume&lt;/code&gt; is the directory mapped on the host system and &lt;code&gt;/data&lt;/code&gt; on the container.&lt;/p&gt;

&lt;h4&gt;
  
  
  Volume Lifecycle:
&lt;/h4&gt;

&lt;p&gt;Volumes are not automatically deleted when a container is removed, preserving the data.&lt;/p&gt;




&lt;p&gt;You can manually remove a volume using this command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker volume rm my_volume
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Inspecting Volumes:
&lt;/h4&gt;

&lt;p&gt;To inspect a volume and view details about it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker volume inspect my_volume
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;In this article, we covered various fundamental aspects of Docker:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic Docker Networking: How containers communicate with each other and external systems, and the different network drivers available.&lt;/li&gt;
&lt;li&gt;Docker Volumes and Persistent Storage: Methods to persist data generated by containers and ensure data persists across container restarts.&lt;/li&gt;
&lt;li&gt;Docker Compose: Tool for defining and running multi-container applications, simplifying orchestration using a YAML file.&lt;/li&gt;
&lt;li&gt;Pushing Docker Images to Docker Hub: Steps to share and manage Docker images on Docker Hub.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By mastering these concepts, you’ll be better equipped to deploy and manage applications using Docker efficiently.&lt;/p&gt;

&lt;p&gt;Let’s connect on &lt;a href="https://www.linkedin.com/in/ideategudy" rel="noopener noreferrer"&gt;LinkedIn here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>containerapps</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Hosting a Static website on a private Amazon S3 bucket using CloudFront’s OAC</title>
      <dc:creator>Goodnews Azonubi </dc:creator>
      <pubDate>Fri, 27 Sep 2024 07:49:26 +0000</pubDate>
      <link>https://dev.to/ideategudy/hosting-a-static-website-on-a-private-amazon-s3-bucket-using-cloudfronts-oac-1b0i</link>
      <guid>https://dev.to/ideategudy/hosting-a-static-website-on-a-private-amazon-s3-bucket-using-cloudfronts-oac-1b0i</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;In this article, we’ll explore how to securely host a static website on a private Amazon S3 bucket using Amazon CloudFront with Origin Access Control (OAC). By following these steps, you can ensure that your website content is accessible only through authorized channels while benefiting from the performance and scalability of CloudFront.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To follow through with this article you need to meet the following requirements&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You should have registered an AWS Account&lt;/li&gt;
&lt;li&gt;Static website files to be uploaded (HTML, CSS, JavaScript)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Amazon S3&lt;/li&gt;
&lt;li&gt;Amazon CloudFront&lt;/li&gt;
&lt;li&gt;Creating an Amazon S3 Bucket&lt;/li&gt;
&lt;li&gt;Uploading Static Website Files&lt;/li&gt;
&lt;li&gt;Setting Up CloudFront and OAC&lt;/li&gt;
&lt;li&gt;Testing Your Setup&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Amazon S3
&lt;/h2&gt;

&lt;p&gt;Amazon Simple Storage Service (Amazon S3) is an object storage service used to store and retrieve any type data i.e text files, video files etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Amazon CloudFront
&lt;/h2&gt;

&lt;p&gt;Amazon CloudFront is an Amazon web service that speeds up the distribution of your static and dynamic web contents securely using a content delivery network (CDN). CloudFront ensures your web contents are accessible globally by caching them in &lt;a href="https://www.lastweekinaws.com/blog/what-is-an-edge-location-in-aws-a-simple-explanation/" rel="noopener noreferrer"&gt;edge locations&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating an Amazon S3 Bucket
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Log in to your AWS Management Console.
Navigate to the S3 service.&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%2Fzodprmfmrk8rxj5tu7ot.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzodprmfmrk8rxj5tu7ot.jpg" alt=" " width="799" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click &lt;code&gt;Create bucket&lt;/code&gt; and enter a unique bucket name&lt;/li&gt;
&lt;li&gt;Scroll down and click &lt;code&gt;Create bucket&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%2Fb77531bvfn3hxcrr3uij.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb77531bvfn3hxcrr3uij.jpg" alt=" " width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Leave other settings as default. By default your bucket is set to private&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%2F5iucjdvlpzm87yb7dq23.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5iucjdvlpzm87yb7dq23.jpg" alt=" " width="798" height="173"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Uploading Static Website Files
&lt;/h2&gt;

&lt;p&gt;Upload your static website files to the S3 bucket.&lt;/p&gt;

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

&lt;p&gt;This is all we are doing here for now&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up CloudFront and OAC
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to the CloudFront service in the AWS Management Console.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;code&gt;Create a CloudFront distribution&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%2Fn6gwoby2dlv5r2xd0tdq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn6gwoby2dlv5r2xd0tdq.jpg" alt=" " width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose your s3 bucket name from the &lt;code&gt;origin domain&lt;/code&gt; pop up.&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%2F6xoppc3miooc7zpeku2k.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xoppc3miooc7zpeku2k.jpg" alt=" " width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Since our bucket is private we need a way to access it privately using CloudFront distribution. To achieve this we need to create an Origin Access Control (OAC) which is a newer way to establish secure connection. Unlike Origin Access Identity (OAI) which is an older way to connect CloudFront distribution to S3, &lt;a href="https://aws.amazon.com/blogs/networking-and-content-delivery/amazon-cloudfront-introduces-origin-access-control-oac/" rel="noopener noreferrer"&gt;Amazon CloudFront introduces Origin Access Control (OAC)&lt;/a&gt; which provides more functionality like signed requests, supports accessing S3 in all AWS regions which includes currently existing and future regions and more features.&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%2Fbnnvc11lwneduypabp2s.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbnnvc11lwneduypabp2s.jpg" alt=" " width="800" height="353"&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%2F1wwvv0iwvgmq3fl35k80.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1wwvv0iwvgmq3fl35k80.jpg" alt=" " width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can choose to enable WAF which adds another layer of security to incoming traffic going to your CloudFront distribution&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%2F2l7gi4bwtpf2k5bxjw3s.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2l7gi4bwtpf2k5bxjw3s.jpg" alt=" " width="798" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But since this is a temporal deployment you can go with the second option&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%2F3lk00v6me3vzabwz3avk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3lk00v6me3vzabwz3avk.jpg" alt=" " width="800" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Now scroll down and click on &lt;code&gt;Create distribution&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Copy Policy generated by CloudFront OAC&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%2F3l3s24uj4pk87c91cjl6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3l3s24uj4pk87c91cjl6.jpg" alt=" " width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s go back to our S3 bucket permission to paste our generated policy&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%2Fxksm11pymrlwkguzc011.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxksm11pymrlwkguzc011.jpg" alt=" " width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paste policy and &lt;code&gt;save changes&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%2Fk32jsh7cul4d19bjgkwm.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk32jsh7cul4d19bjgkwm.jpg" alt=" " width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Testing your setup&lt;br&gt;
Go back to your CloudFront to copy your &lt;code&gt;Distribution domain name&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%2F1p284ea18kz5f8ep4ri3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1p284ea18kz5f8ep4ri3.jpg" alt=" " width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paste on your 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.amazonaws.com%2Fuploads%2Farticles%2Fx8r7ct9nyfy3jsdmpw0m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx8r7ct9nyfy3jsdmpw0m.jpg" alt=" " width="799" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify that your content is served securely via CloudFront.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;In conclusion, by following the steps outlined in this guide, you’ve successfully set up a secure and efficient hosting environment for your static website using Amazon S3, CloudFront, and Origin Access Control (OAC). Your website content is now accessible only through authorized channels, ensuring both security and scalability.&lt;/p&gt;

&lt;p&gt;That’s all for now, I hope you found this helpful 🙂&lt;/p&gt;

&lt;p&gt;Let’s connect on &lt;a href="https://www.linkedin.com/in/ideategudy" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>staticwebapps</category>
      <category>cloudstorage</category>
      <category>devops</category>
    </item>
    <item>
      <title>Laravel LAMP Stack Deployment: Automating Setup with Bash Script on an Ubuntu Server</title>
      <dc:creator>Goodnews Azonubi </dc:creator>
      <pubDate>Fri, 27 Sep 2024 07:25:16 +0000</pubDate>
      <link>https://dev.to/ideategudy/laravel-lamp-stack-deployment-automating-setup-with-bash-script-on-an-ubuntu-server-19kp</link>
      <guid>https://dev.to/ideategudy/laravel-lamp-stack-deployment-automating-setup-with-bash-script-on-an-ubuntu-server-19kp</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;Automating Deployment of a Laravel LAMP Stack Application using Bash Script&lt;/code&gt; introduces an efficient method for deploying web applications built with Laravel, a popular PHP framework known for its elegant syntax and powerful features.&lt;/p&gt;

&lt;p&gt;In this article we’ll explore automating the deployment of a Laravel-based web application on a &lt;code&gt;LAMP (Linux, Apache, MySQL, PHP)&lt;/code&gt; stack using Bash Scripting. By automating this process, we can ensure consistency, reduce manual errors, and speed up deployment times. Whether you’re a developer aiming to simplify deployment or an operations engineer looking to streamline processes, this guide will walk you through the steps to automate your Laravel LAMP stack deployments effectively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Ubuntu Server: Ensure you have an Ubuntu server instance set up and accessible.&lt;/li&gt;
&lt;li&gt;SSH Access: Ensure you can SSH into the Ubuntu server.&lt;/li&gt;
&lt;li&gt;Basic Linux Commands: Familiarity with basic Linux commands and the terminal.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;What is a Bash Script&lt;/li&gt;
&lt;li&gt;Automating Laravel LAMP Stack Installation&lt;/li&gt;
&lt;li&gt;Deploying Laravel Application&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is a Bash Script
&lt;/h2&gt;

&lt;p&gt;A Bash script is a text file containing a series of commands that are executed by the Bash shell (Bourne Again SHell), which is the default command-line interpreter for Unix-like operating systems, including Linux and macOS. Bash scripts are commonly used for automating repetitive tasks, system administration, and complex command-line operations. They can be executed directly from the command line or scheduled to run at specific times using cron jobs or other scheduling tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating Laravel LAMP Stack Installation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Create a script file &lt;code&gt;laravel.sh&lt;/code&gt; and make it an executable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch laravel_app.sh &amp;amp;&amp;amp; chmod +x laravel_app.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will be using &lt;strong&gt;vim&lt;/strong&gt; as our text editor but you can use nano if that’s what you’re familiar with&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Open the file in a text editor and press "i" to enter insert mode.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vim laravel_app.sh 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Let’s start writing our script first by declaring our shebang.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our script will be divided into functions to handle different tasks. You can declare your function in two ways&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function setup() {

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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;setup() {

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

&lt;/div&gt;



&lt;p&gt;and it consists of a&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;function declaration&lt;/strong&gt; — “function” (optional)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;function name&lt;/strong&gt; — “setup”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;function parameters&lt;/strong&gt; — “( )” optional&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;code block&lt;/strong&gt; — “{ }”&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The function parameter can be empty which means the function does not take any arguments&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;A function will not execute unless it is called. To call a function simply type out the function name &lt;code&gt;(setup)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; We need a way to keep track of the line our script is currently running so let’s manipulate the echo command output to make it interactive while running the script. Write a function that changes the colour of our echo output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Function to display messages in gold
function gold_echo() {

    echo -e "\e[38;5;220m$@\e[0m"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5:&lt;/strong&gt; Write a function that installs the lamp stack (Apache, MySQL and PHP).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function install_lamp() {
# Install PHP
gold_echo "---------------------update php repository-----------------------"

 sudo apt update
 sudo add-apt-repository ppa:ondrej/php -y

gold_echo "-----------------------Installing Php8.2-----------------------------------------"
 sudo apt install php8.2 -y

gold_echo "-------------------------------------Installing php dependencies----------------------------"

 sudo apt install php8.2-curl php8.2-dom php8.2-mbstring php8.2-xml php8.2-mysql zip unzip -y

gold_echo "-------------------------- php done ----------------------------------"

#Install Apache web server

gold_echo "----------------------------Installing Apache--------------------------------------------------"

 sudo apt install apache2 -y
 sudo apt update
 sudo systemctl restart apache2

#Install Mysql-server

gold_echo "------------------------------------Installing mysql-server----------------------------------------------"

 sudo apt install mysql-server -y
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check if there are other PHP extensions that are not currently present in your server.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Laravel PHP extensions requirements:&lt;/p&gt;

&lt;p&gt;✅ Ctype PHP Extension&lt;br&gt;
❌ cURL PHP Extension&lt;br&gt;
❌ DOM PHP Extension&lt;br&gt;
✅ Fileinfo PHP Extension&lt;br&gt;
✅ Filter PHP Extension&lt;br&gt;
✅ Hash PHP Extension&lt;br&gt;
❌ Mbstring PHP Extension&lt;br&gt;
✅ OpenSSL PHP Extension&lt;br&gt;
✅ PCRE PHP Extension&lt;br&gt;
✅ PDO PHP Extension&lt;br&gt;
✅ Session PHP Extension&lt;br&gt;
✅ Tokenizer PHP Extension&lt;br&gt;
❌ XML PHP Extension&lt;/p&gt;

&lt;p&gt;The items marked in red were not currently present on my server, so I'll be install them.&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 install php8.2-curl php8.2-dom php8.2-mbstring php8.2-xml php8.2-mysql zip unzip -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to install &lt;code&gt;zip&lt;/code&gt; and &lt;code&gt;unzip&lt;/code&gt; which will be needed later by composer&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6:&lt;/strong&gt; Write a function to install and setup composer&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function composer_setup() {
 cd ~

 if [ -d "$HOME/composer" ]; then

         gold_echo "--------------------------------------Composer Directory Exists--------------------------------------------"
 else
   mkdir composer
   cd composer

   gold_echo "-------------------Directory created successfully--------------------------------"

   curl -sS https://getcomposer.org/installer | php
   sudo mv composer.phar /usr/local/bin/composer

   gold_echo "------------------- Composer Added Successfully-------------------------"
 fi
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 7:&lt;/strong&gt; Write a function that clones and setup laravel app&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function setup_laravel_app() {
 cd /var/www/

 sudo rm -r ./*
 sudo git clone https://github.com/laravel/laravel
 sudo chown -R $USER:$USER laravel
 cd laravel
#Install dependencies using composer
 composer install
 cp .env.example .env
 php artisan key:generate
 sudo chown -R www-data bootstrap/cache
 sudo chown -R www-data storage

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/laravel/laravel" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt; to laravel app&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 8:&lt;/strong&gt; Write a function that configures MySQL Server (create database and user)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function conf_mysql() {
   cd /var/www/laravel

   gold_echo "-------------Setup mysql database and user--------------------------------"

# Configure MySQL database
sudo mysql -uroot -e "CREATE DATABASE laravel_db;"
sudo mysql -uroot -e "CREATE USER 'laravel_user'@'localhost' IDENTIFIED BY '000000';"
sudo mysql -uroot -e "GRANT ALL PRIVILEGES ON laravel_db.* TO 'laravel_user'@'localhost';"
sudo mysql -uroot -e "FLUSH PRIVILEGES;"


gold_echo "-----------------Done with database setup--------------"
gold_echo "------------------Editing .env file--------------------"




   sed -i 's/DB_CONNECTION=sqlite/DB_CONNECTION=mysql/' .env
   sed -i 's/# DB_HOST=127.0.0.1/DB_HOSTS=127.0.0.1/' .env
   sed -i 's/# DB_PORT=3306/DB_PORT=3306/' .env
   sed -i 's/# DB_DATABASE=laravel/DB_DATABASE=laravel_db/' .env
   sed -i 's/# DB_USERNAME=root/DB_USERNAME=laravel_user/' .env
   sed -i 's/# DB_PASSWORD=/DB_PASSWORD=000000/' .env





gold_echo "-------Migrating database--------"

   php artisan migrate

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Username:&lt;/strong&gt; laravel_user&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database name:&lt;/strong&gt; laravel_db&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Password:&lt;/strong&gt; 000000&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 9:&lt;/strong&gt; Write a function to create a new Apache virtual host to point our new laravel app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function apache_conf() {
    #Setup Virtual host for app
    cd ~
    sudo tee /etc/apache2/sites-available/laravel.conf &amp;lt;&amp;lt;EOF
    &amp;lt;VirtualHost *:80 *:3000&amp;gt;
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/laravel/public/

    &amp;lt;Directory /var/www/laravel/public/&amp;gt;
            Options Indexes FollowSymLinks
            AllowOverride All
            Require all granted
    &amp;lt;/Directory&amp;gt;

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
  &amp;lt;/VirtualHost&amp;gt;
EOF


  cd ~
   sudo a2dissite 000-default.conf
   sudo a2enmod rewrite
   sudo a2ensite laravel.conf
   sudo systemctl restart apache2
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 10:&lt;/strong&gt; Write a function to call our functions&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Functions

function main() {
  install_lamp
  composer_setup
  setup_laravel_app
  conf_mysql
  apache_conf
}

#Execute All Functions
main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and exit&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Press ESC key&lt;/li&gt;
&lt;li&gt;colon wq (:wq)&lt;/li&gt;
&lt;li&gt;Enter&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deploying Laravel Application
&lt;/h2&gt;

&lt;p&gt;Execute script&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./laravel_app.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9hhkx9swoik5so3wihht.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9hhkx9swoik5so3wihht.gif" alt=" " width="560" height="295"&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%2Fcd1gmi3qxh0k9e3au1bl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcd1gmi3qxh0k9e3au1bl.jpg" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;This guide has provided a step-by-step approach to automating the deployment of a Laravel LAMP stack application using Bash script on an Ubuntu server. By automating these processes, developers and operations engineers can ensure consistency, reduce errors, and expedite deployment times, leading to a more efficient and streamlined application environment.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>bash</category>
      <category>automation</category>
    </item>
    <item>
      <title>Protecting Sensitive Data using Ansible Vault</title>
      <dc:creator>Goodnews Azonubi </dc:creator>
      <pubDate>Thu, 04 Jul 2024 13:58:07 +0000</pubDate>
      <link>https://dev.to/ideategudy/protecting-sensitive-data-using-ansible-vault-5h7k</link>
      <guid>https://dev.to/ideategudy/protecting-sensitive-data-using-ansible-vault-5h7k</guid>
      <description>&lt;h2&gt;
  
  
  Introduction:
&lt;/h2&gt;

&lt;p&gt;In this tutorial we will explore Ansible Vault which is a feature of ansible that comes pre-installed. We will discuss what Ansible Vault is, and how it can be used for effective management of information such as passwords, API keys, files and other sensitive data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites:
&lt;/h2&gt;

&lt;p&gt;You need to have Ansible installed to be able to follow along with this tutorial. If you don’t have Ansible installed yet follow this tutorial on how to &lt;a href="https://www.cherryservers.com/blog/how-to-install-and-configure-ansible-on-ubuntu-20-04" rel="noopener noreferrer"&gt;install Ansible on Ubuntu 20.04&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Proceed with this guide once your server has been configured with the above requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Table of Contents
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;What is Ansible&lt;/li&gt;
&lt;li&gt;What is Ansible Vault&lt;/li&gt;
&lt;li&gt;How to use Ansible Vault&lt;/li&gt;
&lt;li&gt;Best Practices for Using Ansible Vault&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What is Ansible
&lt;/h3&gt;

&lt;p&gt;Ansible is an open-source automation tool that simplifies IT tasks such as configuration management, application deployment, and orchestration by allowing users to automate repetitive tasks using simple, declarative YAML-based scripts called playbooks.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Ansible Vault
&lt;/h3&gt;

&lt;p&gt;Ansible Vault is a feature of ansible which provides a secure way for managing sensitive information such as API keys, password or even private data within your playbook or file. Ansible Vault uses the AES256 algorithm which is a symmetric form of encryption that uses a single key (or password ) for encrypting and decrypting data unlike the asymmetric that uses a private and public key pair.&lt;/p&gt;

&lt;p&gt;Ansible Vault has several arguments used to manipulate files such as &lt;code&gt;create, edit, view, encrypt, decrypt, rekey, encrypt_string, decrypt_string&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How to use Ansible Vault
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;ansible-vault&lt;/code&gt; command acts as the primary interface for managing encrypted content within Ansible. It facilitates the encryption of files initially and subsequently enables operations such as viewing, editing or decrypting the encrypted data.&lt;/p&gt;

&lt;h4&gt;
  
  
  How to create a new encrypted file
&lt;/h4&gt;

&lt;p&gt;Use the &lt;code&gt;ansible-vault create&lt;/code&gt; command, followed by the name of the file to create a new encrypted file. This command will prompt you to enter and confirm the password for the newly created file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ansible-vault create secret.yml

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3iqd4gvr3bfts878e0u.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3iqd4gvr3bfts878e0u.jpg" alt=" " width="800" height="390"&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%2Fzspkpw2q5eb9186phvey.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzspkpw2q5eb9186phvey.jpg" alt=" " width="799" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your new file will open in your default text editor where you can type your secret texts and save.&lt;/p&gt;

&lt;p&gt;Note: You can access your decrypted texts by providing the password or pass key you provided during encryption process.&lt;/p&gt;

&lt;h4&gt;
  
  
  How to encrypt an existing file
&lt;/h4&gt;

&lt;p&gt;Use the &lt;code&gt;ansible-vault encrypt&lt;/code&gt; command, followed by the name of the file, to encrypt an already existing file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ansible-vault encrypt file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h4&gt;
  
  
  How to view an encrypted file
&lt;/h4&gt;

&lt;p&gt;Use the &lt;code&gt;ansible-vault view&lt;/code&gt;command, followed by the name of the file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ansible-vault view secret.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h4&gt;
  
  
  How to edit an encrypted file
&lt;/h4&gt;

&lt;p&gt;Use the &lt;code&gt;ansible-vault edit&lt;/code&gt; command, followed by the name of the file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ansible-vault edit secret.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h4&gt;
  
  
  How to decrypt an encrypted file
&lt;/h4&gt;

&lt;p&gt;Use the &lt;code&gt;ansible-vault decrypt&lt;/code&gt; command, followed by the name of the file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ansible-vault decrypt file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h4&gt;
  
  
  How to change the password of an encrypted file
&lt;/h4&gt;

&lt;p&gt;Use the &lt;code&gt;ansible-vault rekey&lt;/code&gt; command, followed by the name of the file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ansible-vault rekey secret.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;You will be prompted to enter the current password of the file and afterwards you can enter and confirm the new password&lt;/p&gt;

&lt;h4&gt;
  
  
  Saving your password to a file
&lt;/h4&gt;

&lt;p&gt;Saving your password to a file (make sure the file is not tracking by version control) and specifying the path to the file is also another way of performing different operations without typing the password always on the terminal prompt.&lt;/p&gt;

&lt;p&gt;This password should be auto generated by a password generator software and not hard coded to increase security.&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%2Fqynufiaebauysb1zylql.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqynufiaebauysb1zylql.jpg" alt=" " width="800" height="457"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.avast.com/random-password-generator#pc" rel="noopener noreferrer"&gt;Random Password Generator&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%2Fii4mz9axvdwwjgn1y0sw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fii4mz9axvdwwjgn1y0sw.jpg" alt=" " width="799" height="161"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Random Password Generator
&lt;/h4&gt;

&lt;p&gt;This key should be kept private and should not be committed to version control&lt;/p&gt;
&lt;h4&gt;
  
  
  How to decrypt an encrypted file during playbook run-time
&lt;/h4&gt;

&lt;p&gt;Let’s say for instance you encrypt your inventory/hosts file that has the IP address of your slave servers, you can run your playbook without decrypting first. Just specify the path to your password file in your command or input the password before playbook runs&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%2F9pyelbda6bzw0dkghuqr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9pyelbda6bzw0dkghuqr.jpg" alt=" " width="800" height="677"&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%2F7jgr4j8uwzancxbdjhy8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7jgr4j8uwzancxbdjhy8.jpg" alt=" " width="799" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;— — ask-vault-pass:&lt;/code&gt; This will prompt you to input your password&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ansible-playbook -i ../hosts main.yml --key-file ~/.ssh/ansible --ask-vault-pass
&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%2Fcvrjch14uzo5lt9x6k8j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcvrjch14uzo5lt9x6k8j.jpg" alt=" " width="799" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;- - vault-password-file:&lt;/code&gt; This will use the password file directly without asking for password&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ansible-playbook -i ../hosts main.yml --key-file ~/.ssh/ansible --vault-password-file ~/ansible_vault/vault_pass.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h4&gt;
  
  
  Using encrypted variables in playbook
&lt;/h4&gt;

&lt;p&gt;You can access an encrypted variable file using the normal method by including your variable file in your playbook&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
- name: Configure Servers (Ubuntu and CentOS)
  hosts: all
  vars_files:
    - secret_vars.yml

  become: true
  tasks:

    - name: Update Repository Index (Ubuntu and CentOS)
      package:
        update_cache: yes
      changed_when: false

    - name: Clone github repo
      git:
        repo: "{{ github_repo }}"
        dest: "/home/vagrant/test"
        force: yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Best Practices for Using Ansible Vault
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use Strong Passwords: Ensure passwords are complex and secure.&lt;/li&gt;
&lt;li&gt;Version Control: Track encrypted files in version control but never push your password file to it&lt;/li&gt;
&lt;li&gt;Backup Encrypted Files: Prevent data loss with regular backups.&lt;/li&gt;
&lt;li&gt;Password Access: Regularly audit who has access to view your password file.. you can use the chmod to set permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Ansible Vault is a useful tool for managing secret information stored in files by encryption and decryption. To learn more about ansible vault visit the official &lt;a href="https://docs.ansible.com/ansible/latest/vault_guide/index.html" rel="noopener noreferrer"&gt;ansible-vault documentation&lt;/a&gt; page.&lt;/p&gt;

</description>
      <category>ansible</category>
      <category>devops</category>
      <category>cloudcomputing</category>
      <category>security</category>
    </item>
    <item>
      <title>User Management Automation Using Bash Script</title>
      <dc:creator>Goodnews Azonubi </dc:creator>
      <pubDate>Thu, 04 Jul 2024 12:53:00 +0000</pubDate>
      <link>https://dev.to/ideategudy/user-management-automation-using-bash-script-1p4o</link>
      <guid>https://dev.to/ideategudy/user-management-automation-using-bash-script-1p4o</guid>
      <description>&lt;h2&gt;
  
  
  INTRODUCTION
&lt;/h2&gt;

&lt;p&gt;As a SysOps (Systems Operations) engineer, managing user accounts and permissions is a critical task. Ensuring that users are properly created, assigned to the correct groups, and have secure passwords is essential for maintaining the security and efficiency of the system. A script file (&lt;code&gt;create_users.sh&lt;/code&gt;) will be created to automate the process of user and group creation, making it easier to manage large numbers of users and maintain a secure environment. This is a stage 1 DevOps task given at the &lt;a href="https://hng.tech/internship" rel="noopener noreferrer"&gt;HNG 11 INTERNSHIP PROGRAM&lt;/a&gt;.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Basic knowledge of the Linux CLI (command line interface).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Script Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash


# Check if the script is run as root user
if [[ $EUID -ne 0 ]]; then
  echo "This script must be run as root"
  exit 1
fi


# Check if the input file is provided in argument (contains the usernames and groups)
if [ -z "$1" ]; then
  echo "Usage: sudo $0 &amp;lt;filename&amp;gt;"
  exit 1
fi

INPUT_FILE=$1
LOG_FILE="/var/log/user_management.log"

# Create a secure directory to save secret files
mkdir -p /var/secure
chmod 700 /var/secure

# Create secure passwords file
PASSWORD_FILE="/var/secure/user_passwords.txt"
echo "USERNAME  |   PASSWORD" &amp;gt; "$PASSWORD_FILE"
echo "---------------------------------------------" &amp;gt;&amp;gt; "$PASSWORD_FILE"
chmod 600 "$PASSWORD_FILE"



# Read input file line by line
while IFS=';' read -r username groups; do

   # Remove leading/trailing whitespaces
     username=$(echo "$username" | xargs)
     groups=$(echo "$groups" | xargs)

   # Skip empty lines
     if [ -z "$username" ]; then
       continue
     fi


  # Create the user with a personal group
    if id "$username" &amp;amp;&amp;gt;/dev/null; then
      echo "----------------------------------------------------------------" | tee -a "$LOG_FILE"
      echo "User $username already exists" | tee -a "$LOG_FILE"
    else
      useradd -m -U "$username"
      echo "----------------------------------------------------------------" | tee -a "$LOG_FILE"
      echo "User $username created with a personal group" | tee -a "$LOG_FILE"
    fi


  # Create additional groups and assign the user to them
  if [ -n "$groups" ]; then
    IFS=',' read -ra groupName &amp;lt;&amp;lt;&amp;lt; "$groups"
    for group in "${groupName[@]}"; do
      group=$(echo "$group" | xargs)  # Remove leading/trailing whitespaces
      if ! getent group "$group" &amp;amp;&amp;gt;/dev/null; then
        groupadd "$group"
        echo "group: $group created successfully"
      fi
      usermod -aG "$group" "$username"
      echo "user: $username added to group: $group"
    done
  fi


    # Generate, set and store password securely
    password=$(openssl rand -base64 12)
    echo "$username:$password" | chpasswd
    echo "$username |   $password" &amp;gt;&amp;gt; "$PASSWORD_FILE"



    # Set permissions and ownership for the home directory
    chown -R "$username":"$username" "/home/$username"
    chmod 700 "/home/$username"

    echo "-----------------------------------------------------------------------------------"
    echo "  "

    # Log recent actions
    echo "$(date '+%Y-%m-%d %H:%M:%S') - Created user with username: $username and groups: $groups" &amp;gt;&amp;gt; "$LOG_FILE"
    echo "-------------------------------------------------------------------" &amp;gt;&amp;gt; "$LOG_FILE"
done &amp;lt; "$INPUT_FILE"

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Automated User and Group Creation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The script reads from a file containing user and group information and automates the creation of users and their respective groups.&lt;/li&gt;
&lt;li&gt;Personal groups are created for each user, ensuring clear ownership and security.
Group Assignment:&lt;/li&gt;
&lt;li&gt;Users can be assigned to multiple groups, facilitating organized and efficient permission management.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Secure Password Generation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Random passwords are generated for each user, enhancing security.&lt;/li&gt;
&lt;li&gt;Passwords are stored securely in a file with restricted access, ensuring that only authorized personnel can view them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Logging and Documentation:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Actions performed by the script are logged to a file, providing an audit trail for accountability and troubleshooting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1 Input File:&lt;/strong&gt; The script takes an input file containing the list and users and groups they are to be added. it is formatted as &lt;code&gt;user;groups&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;light; engineering,marketing,drama
idimma; drama,product
mayowa; hng-premium,design
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Script file:&lt;/strong&gt; You need to first make sure your script is executable by using this command &lt;code&gt;chmod +x create_users.sh&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Execute the script with root privileges to ensure it can create users and groups and manage passwords.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo bash create_users.sh &amp;lt;filename&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Output:&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%2Fdq3wb715s34io5co39cp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdq3wb715s34io5co39cp.jpg" alt=" " width="799" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Passwords are securely stored in &lt;code&gt;/var/secure/user_passwords.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;All actions are logged to &lt;code&gt;/var/log/user_management.log&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%2Fvrgrem4il8o1pqoeis6t.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvrgrem4il8o1pqoeis6t.jpg" alt=" " width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can checkout for available roles at HNG &lt;a href="https://hng.tech/hire" rel="noopener noreferrer"&gt;here&lt;/a&gt; and register for the next &lt;a href="https://hng.tech/internship" rel="noopener noreferrer"&gt;HNG Internship Cohort&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Link to my script and file: &lt;a href="https://github.com/ideateGudy/User-Management-Script-Using-Bash" rel="noopener noreferrer"&gt;https://github.com/ideateGudy/User-Management-Script-Using-Bash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>automation</category>
      <category>linux</category>
      <category>bash</category>
    </item>
  </channel>
</rss>
