<?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.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>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="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%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="359"&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="326"&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="800" 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="331"&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="345"&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="800" 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="497"&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="610"&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="800" 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="800" 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="636"&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="522"&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="471"&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="800" 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="800" 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="215"&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="800" 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="111"&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="800" 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="271"&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="800" 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="470"&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%2Cformat=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%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpn8aujbgm66mp47w91jt.gif" alt=" " width="600" height="337"&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="154"&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="800" 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="524"&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="377"&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="313"&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="800" 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="387"&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="800" 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="361"&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="368"&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="800" 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="347"&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="800" 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%2Cformat=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%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9hhkx9swoik5so3wihht.gif" alt=" " width="560" height="294"&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="389"&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="800" 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="492"&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="800" 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="447"&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="800" 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="456"&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="800" 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="800" 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="800" 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="800" 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="800" 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="333"&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>
