<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Meir Gabay</title>
    <description>The latest articles on Forem by Meir Gabay (@unfor19).</description>
    <link>https://forem.com/unfor19</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%2F223096%2F957afc26-d94b-4152-bf5c-6f38d1c8f38e.jpeg</url>
      <title>Forem: Meir Gabay</title>
      <link>https://forem.com/unfor19</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/unfor19"/>
    <language>en</language>
    <item>
      <title>AI Code Generation, Smarter and More Cost-Efficient with Context Engineering</title>
      <dc:creator>Meir Gabay</dc:creator>
      <pubDate>Mon, 21 Jul 2025 07:18:45 +0000</pubDate>
      <link>https://forem.com/unfor19/ai-code-generation-smarter-and-more-cost-efficient-with-context-engineering-1bjh</link>
      <guid>https://forem.com/unfor19/ai-code-generation-smarter-and-more-cost-efficient-with-context-engineering-1bjh</guid>
      <description>&lt;p&gt;We've all hit that wall: endless tweaks to prompts, fixing AI-generated bugs, and wondering why such powerful tech feels so hit-or-miss. It often feels like working with a brilliant but forgetful junior developer who hasn't been onboarded to your project.&lt;/p&gt;

&lt;p&gt;The problem isn't the model's power; it's the context we give it. Or rather, the context we &lt;em&gt;don't&lt;/em&gt; give it.&lt;/p&gt;

&lt;p&gt;This post is for you if you've ever been frustrated by an AI assistant that just doesn't "get" your project. We'll explore how a well-crafted &lt;code&gt;DETAILS.md&lt;/code&gt; can save you time, reduce costs, and dramatically improve the quality of your AI-generated code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://arxiv.org/abs/2406.10279" rel="noopener noreferrer"&gt;Research shows&lt;/a&gt; models hallucinate packages; The average percentage of hallucinated packages is at least 5.2% for commercial models and 21.7% for open-source models.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The High Cost of Runtime Research
&lt;/h2&gt;

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

&lt;p&gt;When an AI assistant lacks a high-level overview of your project, it's forced to build one on the fly. It skims through your files, trying to piece together the architecture, dependencies, and coding conventions. This "runtime research" is not only slow, but it's also incredibly expensive. Every file it reads, every line it analyzes, adds to the token count of your API call. This translates directly to higher costs.&lt;/p&gt;

&lt;p&gt;More importantly, the results are often suboptimal. The AI might generate code that works in isolation but clashes with your existing patterns, ignores crucial utility functions, or re-invents a wheel you already have spinning smoothly in another module. It's a frustrating cycle of generating, correcting, and re-generating that kills productivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Solution: The &lt;code&gt;DETAILS.md&lt;/code&gt; File
&lt;/h2&gt;

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

&lt;p&gt;A &lt;code&gt;DETAILS.md&lt;/code&gt; file acts as a concise, high-level "cheat sheet" for your AI assistant. It's not meant to replace your full documentation, but to provide an essential, distilled overview of the project. Think of it as the onboarding document you'd give a new human developer.&lt;/p&gt;

&lt;p&gt;A great &lt;code&gt;DETAILS.md&lt;/code&gt; file typically includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Project Overview:&lt;/strong&gt; What does the project do? What problem does it solve? Who are the target users?&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Architecture:&lt;/strong&gt; A high-level description of the project's structure (e.g., microservices, monolithic, MVC). A link to an architecture diagram is a huge plus.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Key Components:&lt;/strong&gt; A list of the most important files, directories, modules, or classes and their purpose. This helps the AI know where to look for relevant code.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Coding Conventions &amp;amp; Stack:&lt;/strong&gt; The programming languages and versions (&lt;code&gt;Python 3.11&lt;/code&gt;), frameworks (&lt;code&gt;Express v4&lt;/code&gt;), and any specific patterns, libraries, or style guides (&lt;code&gt;PEP 8&lt;/code&gt;) to follow.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;"How-To" Guides:&lt;/strong&gt; Simple instructions for common tasks like running tests, setting up the local environment, or interacting with the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By providing this information upfront, you stop treating your AI like a black box and start treating it like a team member. It no longer needs to guess or reverse-engineer your project's context.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤔 Why This Matters
&lt;/h2&gt;

&lt;p&gt;Adopting this practice isn't just a theoretical exercise. It delivers massive, measurable improvements in efficiency, cost, and reliability.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Without a &lt;code&gt;DETAILS.md&lt;/code&gt; File&lt;/th&gt;
&lt;th&gt;With a &lt;code&gt;DETAILS.md&lt;/code&gt; File&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code Quality &amp;amp; Accuracy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Frequent hallucinations &amp;amp; bugs&lt;/td&gt;
&lt;td&gt;Noticeably lower complexity, fewer code-smell warnings, and a steep drop in after-merge defects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost &amp;amp; Token Consumption&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High token use, lots of retries&lt;/td&gt;
&lt;td&gt;Prompts stay short; fewer tokens are sent to the API, cutting spend dramatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Development Time &amp;amp; Velocity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Endless cycles of prompt-fix-repeat&lt;/td&gt;
&lt;td&gt;Teams finish tasks faster and junior devs ramp up in days instead of weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Project-Specific Adherence&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ignores your patterns &amp;amp; standards&lt;/td&gt;
&lt;td&gt;Suggestions follow your own architecture and style guidelines&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Automating Your Context: Human-Focused vs. Agent-Focused Tools
&lt;/h2&gt;

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

&lt;p&gt;Manually creating and maintaining a &lt;code&gt;DETAILS.md&lt;/code&gt; file is a great start, but it can become a chore. This is where automated tools come in, but it's crucial to distinguish between tools designed for humans and tools designed for AI agents.&lt;/p&gt;

&lt;p&gt;A great example of a human-oriented tool is &lt;strong&gt;&lt;a href="https://docs.devin.ai/work-with-devin/deepwiki" rel="noopener noreferrer"&gt;Devin's DeepWiki&lt;/a&gt;&lt;/strong&gt;. It excels at indexing a repository to produce a searchable, human-readable wiki with architecture diagrams and documentation. It's fantastic for helping a &lt;em&gt;human developer&lt;/em&gt; get up to speed on an unfamiliar codebase.&lt;/p&gt;

&lt;p&gt;However, when you need to provide context to an &lt;em&gt;AI agent&lt;/em&gt; to perform a specific task, you need a different approach. This is where agent-oriented tools shine.&lt;/p&gt;

&lt;p&gt;For instance, some tools are now being built specifically for this purpose. A good friend of mine developed one called &lt;strong&gt;&lt;a href="https://detailer.ginylil.com/" rel="noopener noreferrer"&gt;Detailer&lt;/a&gt;&lt;/strong&gt; that connects to a GitHub repository and automatically generates a &lt;code&gt;DETAILS.md&lt;/code&gt; file tailored for AI agents. It's a neat approach that creates a standardized starting point that can be used across different platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  One Context to Rule Them All: Symlinking for Different Agents
&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%2F5rfct8q54d4dn4rofsma.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5rfct8q54d4dn4rofsma.webp" alt="one-ring" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of maintaining separate &lt;a href="https://www.anthropic.com/claude-code" rel="noopener noreferrer"&gt;CLAUDE.md - for Claude Code&lt;/a&gt;, &lt;a href="https://blog.google/technology/developers/introducing-gemini-cli-open-source-ai-agent/" rel="noopener noreferrer"&gt;GEMINI.md - for Gemini CLI&lt;/a&gt;, or &lt;a href="https://github.com/openai/codex?tab=readme-ov-file#openai-codex-cli" rel="noopener noreferrer"&gt;AGENTS.md - for OpenAI Codex&lt;/a&gt; files, you can use a tool like &lt;a href="https://detailer.ginylil.com" rel="noopener noreferrer"&gt;Detailer&lt;/a&gt; to generate a single, canonical &lt;code&gt;DETAILS.md&lt;/code&gt;. You can then use symbolic links to point different agent configurations to this one source of truth.&lt;/p&gt;

&lt;p&gt;For example, you can set up your project like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Symlink it for various AI coding tools&lt;/span&gt;
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; DETAILS.md CLAUDE.md      &lt;span class="c"&gt;# For Claude Code&lt;/span&gt;
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; DETAILS.md GEMINI.md      &lt;span class="c"&gt;# For Gemini CLI&lt;/span&gt;
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; DETAILS.md AGENTS.md      &lt;span class="c"&gt;# For OpenAI Codex&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're using an IDE like &lt;a href="https://cursor.com/" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt; or &lt;a href="https://windsurf.com/editor" rel="noopener noreferrer"&gt;Windsurf&lt;/a&gt;, you can add a rule to use the &lt;code&gt;DETAILS.md&lt;/code&gt; file as the context for the agent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALWAYS read @DETAILS.md before taking any action to get context.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This elegant approach ensures that every agent-be it OpenAI Codex, Gemini CLI, Claude, or an IDE like Cursor-gets the same, up-to-date context without any duplication of effort. It's the ultimate "write once, inform everywhere" strategy for context engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Biggest Pitfall: LLMs Don't Read Like You (Understanding Context Rot)
&lt;/h2&gt;

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

&lt;p&gt;Here’s a critical insight from AI research that explains &lt;em&gt;why&lt;/em&gt; a focused &lt;code&gt;DETAILS.md&lt;/code&gt; is so important: &lt;strong&gt;LLMs don't read like humans do.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We assume that if we paste a bunch of files into the prompt, the model will read and understand all of it equally. But that's not what happens. Multiple studies have confirmed that models pay the most attention to the &lt;strong&gt;very beginning and very end&lt;/strong&gt; of the context window. Information stuck in the middle has a much higher chance of being ignored or forgotten-a phenomenon explained by the &lt;a href="https://arxiv.org/abs/2406.16008" rel="noopener noreferrer"&gt;"lost-in-the-middle" problem&lt;/a&gt;. The research demonstrates that LLMs have a strong "U-shaped" attention bias, paying the most attention to the start and end of the context window, regardless of where the most relevant information is.&lt;/p&gt;

&lt;p&gt;This phenomenon is known as &lt;strong&gt;"&lt;a href="https://research.trychroma.com/context-rot" rel="noopener noreferrer"&gt;Context Rot&lt;/a&gt;."&lt;/strong&gt; The longer and more unfocused your context is, the more likely the AI is to get lost. A huge, million-token context window is useless if it's mostly filled with irrelevant "noise" that drowns out the "signal." This is why a concise &lt;code&gt;DETAILS.md&lt;/code&gt; is often more effective than naively dumping your entire codebase into a prompt. The goal isn't to provide &lt;em&gt;more&lt;/em&gt; information, but &lt;em&gt;more relevant&lt;/em&gt; information, placed strategically where the model will see it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway: Context is Everything
&lt;/h2&gt;

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

&lt;p&gt;If you're frustrated with your AI coding tools, don't blame the model. The biggest gains in code quality, speed, and cost-efficiency don't come from switching to the "latest and greatest" LLM. They come from mastering the art and science of providing high-quality context.&lt;/p&gt;

&lt;p&gt;Start small. Create a &lt;code&gt;DETAILS.md&lt;/code&gt; for your current project. Be explicit about your architecture and standards. And watch as your AI assistant transforms from a clumsy, unpredictable intern into a valuable, reliable member of your team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to Dig Deeper?
&lt;/h2&gt;

&lt;p&gt;If you want to explore more about this stuff, here are some of the excellent resources I used to inform this post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Overviews and Mindset:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.philschmid.de/context-engineering" rel="noopener noreferrer"&gt;The New Skill in AI is Not Prompting, It's Context Engineering&lt;/a&gt; - A great high-level overview by Phil Schmid.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ai.intellectronica.net/context-engineering" rel="noopener noreferrer"&gt;Context Engineering: A Primer&lt;/a&gt; - By Addy Osmani, a must-read for programmers.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Academic and Research Papers:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://arxiv.org/abs/2401.08500" rel="noopener noreferrer"&gt;Code Generation with AlphaCodium: From Prompt Engineering to Flow Engineering&lt;/a&gt; - A deep, academic look at going beyond simple prompts to multi-step "flows".&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://arxiv.org/html/2407.11550v2" rel="noopener noreferrer"&gt;The Larger the Better? Improved LLM Code-Generation via Budget Reallocation&lt;/a&gt; - Research showing that running smaller models multiple times with better context can outperform one large model.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://arxiv.org/abs/2304.15004" rel="noopener noreferrer"&gt;Are Emergent Abilities of LLMs a Mirage?&lt;/a&gt; - Foundational paper showing how performance on complex tasks is often a result of better prompting and context.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://arxiv.org/abs/2404.08335" rel="noopener noreferrer"&gt;Toward a Theory of Tokenization in LLMs&lt;/a&gt; - Explains why proper tokenization and context optimization are crucial for code generation.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Best Practices:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.cursor.com/guides/working-with-context" rel="noopener noreferrer"&gt;Cursor Docs: Working with Context&lt;/a&gt; - Practical advice from a leading AI-first editor.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.anthropic.com/engineering/claude-code-best-practices" rel="noopener noreferrer"&gt;Claude Code Best Practices&lt;/a&gt; - From Anthropic's engineering team.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Cost Optimization:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/microsoft/LLMLingua" rel="noopener noreferrer"&gt;LLMLingua: Compressing Prompts for Massive Cost Savings&lt;/a&gt; - A Microsoft Research project for prompt compression.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.helicone.ai/blog/monitor-and-optimize-llm-costs" rel="noopener noreferrer"&gt;How to Monitor Your LLM API Costs and Cut Spending by 90%&lt;/a&gt; - Practical tips on reducing costs, many of which relate to better context.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.linkedin.com/pulse/how-slash-llm-costs-80-guide-2025-atul-yadav-ntwrc" rel="noopener noreferrer"&gt;LLM Cost Optimization: How To Run Gen AI Apps Cost-Efficiently&lt;/a&gt; - More on balancing cost and performance.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;What are your biggest frustrations with AI code generation? Have you tried any of these techniques, or do you have others that work well? I'd love to hear about it!&lt;/em&gt; 🤘 &lt;/p&gt;

</description>
      <category>ai</category>
      <category>contextengineering</category>
      <category>promptengineering</category>
      <category>llm</category>
    </item>
    <item>
      <title>Beyond the Hype: Rediscovering Why Containers Won</title>
      <dc:creator>Meir Gabay</dc:creator>
      <pubDate>Thu, 17 Jul 2025 05:58:02 +0000</pubDate>
      <link>https://forem.com/unfor19/beyond-the-hype-rediscovering-why-containers-won-3k4l</link>
      <guid>https://forem.com/unfor19/beyond-the-hype-rediscovering-why-containers-won-3k4l</guid>
      <description>&lt;p&gt;Ever feel like you missed the memo on why everyone's obsessed with containers? Like, you know Docker exists, you've probably used it, but you're still wondering &lt;em&gt;why&lt;/em&gt; it became the thing that basically took over infrastructure?&lt;/p&gt;

&lt;p&gt;I was having this exact conversation with a colleague last week. They asked me, "Why don't we just run each app on its own tiny VM?" And honestly? It's a fair question. Let me walk you through why containers didn't just win by accident-they solved real problems that were driving us all crazy.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This post is for anyone who's ever thought: "Okay, containers are everywhere, but why exactly?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🚂 The Real Difference: Containers vs VMs
&lt;/h2&gt;

&lt;p&gt;Here's the thing-containers and VMs both do isolation, but they're solving it in completely different ways. Think of it like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you get&lt;/th&gt;
&lt;th&gt;Containers (Docker &amp;amp; friends)&lt;/th&gt;
&lt;th&gt;Virtual Machines&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;How they isolate stuff&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Share the kernel, separate processes&lt;/td&gt;
&lt;td&gt;Each gets its own full OS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;How fast they start&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Seconds (sometimes milliseconds!)&lt;/td&gt;
&lt;td&gt;Minutes (ugh)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory footprint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Megabytes&lt;/td&gt;
&lt;td&gt;Gigabytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;How many per server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tons&lt;/td&gt;
&lt;td&gt;Limited by all that OS overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;"It works on my machine"&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Actually solved this&lt;/td&gt;
&lt;td&gt;Still a problem sometimes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Security&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Good, but shared kernel = shared risk&lt;/td&gt;
&lt;td&gt;Stronger isolation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I remember when we used to spin up VMs for every little service. Want to test something? Wait 5 minutes for the VM to boot. Need to scale up? Hope you've got enough RAM for all those guest operating systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  🏎️ Why Containers Feel Like Magic (Spoiler: They're Not Magic)
&lt;/h2&gt;

&lt;p&gt;The first time I ran &lt;code&gt;docker run hello-world&lt;/code&gt; and it just... worked in seconds, I was hooked. But why are they so much faster?&lt;/p&gt;

&lt;p&gt;Think about it this way: When you boot a VM, you're basically starting up a whole computer inside your computer. The hypervisor has to pretend to be hardware, the guest OS has to go through its entire boot process, initialize all its services-it's a lot.&lt;/p&gt;

&lt;p&gt;Containers? They're just processes. Really well-isolated processes, but still just processes. They use your existing kernel and just sandbox everything else. So you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Instant startup&lt;/strong&gt; (I'm talking milliseconds to seconds)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Way less memory waste&lt;/strong&gt; (no duplicate OS copies eating RAM)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More bang for your buck&lt;/strong&gt; (run way more stuff on the same hardware)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smaller cloud bills&lt;/strong&gt; (seriously, this adds up)&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%2Fmwe2d3e4isa3oiu82ug8.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%2Fmwe2d3e4isa3oiu82ug8.gif" alt="Run nginx locally" width="720" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔒 The Security Reality Check
&lt;/h2&gt;

&lt;p&gt;Let's be honest here-containers aren't some magical security fortress. They share the host kernel, which means if someone finds a kernel exploit, they could potentially break out of all containers on that host.&lt;/p&gt;

&lt;p&gt;VMs? Each one has its own OS and runs through a hypervisor. That's like having separate apartments vs. separate rooms with really good locks.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Containers:&lt;/strong&gt; Super fast and efficient, but if the building has a problem, everyone feels it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VMs:&lt;/strong&gt; Slower and heavier, but each one is its own fortress&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why a lot of smart teams run containers &lt;em&gt;inside&lt;/em&gt; VMs. You get the speed and efficiency of containers with an extra layer of VM isolation. Best of both worlds, if you can afford the complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 The "It Works on My Machine" Problem (Finally Solved!)
&lt;/h2&gt;

&lt;p&gt;You know that feeling when your code works perfectly on your laptop but explodes in production? Yeah, containers basically ended that nightmare.&lt;/p&gt;

&lt;p&gt;I used to spend &lt;em&gt;hours&lt;/em&gt; debugging environment differences. "Oh, you're running Python 3.8 but production has 3.7." "Wait, did anyone install that dependency on the staging server?" "Why is this library behaving differently on Ubuntu vs CentOS?"&lt;/p&gt;

&lt;p&gt;Containers package everything together-your app, its dependencies, the runtime, even specific library versions. When you ship a container, you're shipping the exact environment your code was tested in.&lt;/p&gt;

&lt;p&gt;This revolutionized how we build and deploy software:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No more environment drift&lt;/strong&gt; between dev, staging, and production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD pipelines&lt;/strong&gt; that actually work consistently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy rollbacks&lt;/strong&gt; (just swap container images)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language mixing&lt;/strong&gt; (Python microservice next to a Go service? No problem)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DevOps teams love this because they can deploy and test stuff way faster. As one wise person said: "Containerization complements DevOps because software can be deployed and tested faster, improving feedback loops." &lt;a href="https://octopus.com/blog/benefits-of-containerization#:~:text=DevOps%20is%20a%20practice%20that,that%20improves%20flexibility%20and%20agility." rel="noopener noreferrer"&gt;Octopus Deploy&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚖️ Why CFOs Love Containers Too
&lt;/h2&gt;

&lt;p&gt;Here's something that sold containers to the business side: they save money. Real money.&lt;/p&gt;

&lt;p&gt;When you can run 10x more applications on the same hardware, that means fewer servers to buy, maintain, and power. Your cloud bills get smaller because you're not paying for a bunch of idle operating systems sitting around doing nothing.&lt;/p&gt;

&lt;p&gt;I saw one study that said IBM found containers can cut server maintenance, administration, and facilities costs by about 75% compared to VMs. Now, take that with a grain of salt because these studies always sound too good to be true, but the basic math makes sense-less waste means lower costs. &lt;a href="https://www.namutech.io/blog/cost-benefit-analysis-virtual-machines-vs-containers" rel="noopener noreferrer"&gt;NAMU Tech&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%2F1c9etoqcogdx1oe1f4g6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1c9etoqcogdx1oe1f4g6.webp" alt="CFO Loves it" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ Kubernetes: When Containers Got Superpowers
&lt;/h2&gt;

&lt;p&gt;Containers by themselves are cool, but when you combine them with orchestration platforms like Kubernetes? That's when things get really interesting.&lt;/p&gt;

&lt;p&gt;Kubernetes basically gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auto-scaling&lt;/strong&gt; (traffic spike? More containers appear automatically)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-healing&lt;/strong&gt; (container crashes? New one starts up)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-downtime deployments&lt;/strong&gt; (rolling updates like a boss)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service discovery&lt;/strong&gt; (containers can find each other without hardcoded IPs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The adoption numbers are pretty crazy-apparently 96% of organizations are using Kubernetes now. That's not just hype; that's because it actually solves real operational problems. &lt;a href="https://edgedelta.com/company/blog/kubernetes-adoption-statistics" rel="noopener noreferrer"&gt;Edge Delta&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🧩 When You Should Still Use VMs (Yes, Really)
&lt;/h2&gt;

&lt;p&gt;Look, I'm not here to tell you containers are the answer to everything. Sometimes VMs still make more sense:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Legacy apps&lt;/strong&gt; that were built assuming they own a whole machine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance requirements&lt;/strong&gt; that demand hardware-level isolation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixed OS environments&lt;/strong&gt; (need Windows and Linux on the same host? VMs got you)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Really untrusted workloads&lt;/strong&gt; (like running customer code)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most companies I know run a hybrid setup. Containers for new, cloud-native stuff. VMs for legacy systems and security-sensitive workloads. And often, containers running inside VMs for that extra security layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔮 What's Next: The Lines Are Blurring
&lt;/h2&gt;

&lt;p&gt;The future is getting weird in a good way. New technologies like &lt;a href="https://aws.amazon.com/blogs/aws/firecracker-lightweight-virtualization-for-serverless-computing/" rel="noopener noreferrer"&gt;AWS Firecracker&lt;/a&gt; and serverless containers (&lt;a href="https://aws.amazon.com/fargate/" rel="noopener noreferrer"&gt;AWS Fargate&lt;/a&gt;, &lt;a href="https://cloud.google.com/run" rel="noopener noreferrer"&gt;Google Cloud Run&lt;/a&gt;) are basically giving you VM-level security with container-level performance.&lt;/p&gt;

&lt;p&gt;There are also these things called &lt;a href="https://www.koyeb.com/blog/what-is-a-microvm" rel="noopener noreferrer"&gt;micro-VMs&lt;/a&gt; that start almost as fast as containers but give you better isolation. It's like getting the best of both worlds without having to choose.&lt;/p&gt;

&lt;p&gt;We're moving toward a world where you don't really have to pick sides-you just pick the right tool for each job.&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 Want to Dig Deeper?
&lt;/h2&gt;

&lt;p&gt;If you want to explore more about this stuff, here are some resources I found helpful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.netdata.cloud/academy/container-vs-vm-which-is-better-for-you/" rel="noopener noreferrer"&gt;Netdata: Container vs VM - Which Is Better Option For You&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.veeam.com/blog/virtualization-vs-containerization.html" rel="noopener noreferrer"&gt;Veeam: Virtualization vs. Containerization: Key Differences&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.open-telekom-cloud.com/en/blog/cloud-computing/container-vs-vm" rel="noopener noreferrer"&gt;Open Telekom Cloud: Container or virtual machine – which is more suitable for your project?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloudsecurityalliance.org/blog/2020/12/11/are-containers-more-secure-than-vms" rel="noopener noreferrer"&gt;Cloud Security Alliance: Are Containers More Secure Than VMs?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://edgedelta.com/company/blog/kubernetes-adoption-statistics" rel="noopener noreferrer"&gt;Edge Delta: Latest Kubernetes Adoption Statistics: Global Insights and Analysis for 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://octopus.com/blog/benefits-of-containerization" rel="noopener noreferrer"&gt;Octopus Deploy: The benefits of containerization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://circleci.com/blog/benefits-of-containerization/" rel="noopener noreferrer"&gt;CircleCI: Benefits of containerization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cloud-kinetics.com/blog/enabling-ci-cd-pipeline-for-container-based-workloads/" rel="noopener noreferrer"&gt;Cloud Kinetics: CI/CD Pipeline For Container-Based Workloads: A DevOps Strategy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.namutech.io/blog/cost-benefit-analysis-virtual-machines-vs-containers" rel="noopener noreferrer"&gt;NAMU Tech: Cost benefit Analysis: Virtual Machines vs Containers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/resources/containerize-your-infrastructure" rel="noopener noreferrer"&gt;Google Cloud: The future of infrastructure will be containerized&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.trianz.com/insights/benefits-of-containerization" rel="noopener noreferrer"&gt;Trianz: Benefits of Containerization: Accelerate your SDLC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://northflank.com/blog/your-containers-arent-isolated-heres-why-thats-a-problem-micro-vms-vmms-and-container-isolation" rel="noopener noreferrer"&gt;Northflank: Your containers aren't isolated. Here's why that's a problem.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nanovms.com/blog/problem-container-isolation-zero-cve-images" rel="noopener noreferrer"&gt;NanoVMs: The Problem with Container Isolation and Zero CVE Images&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thenewstack.io/what-we-wish-we-knew-about-container-security/" rel="noopener noreferrer"&gt;The New Stack: What We Wish We Knew About Container Security&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ✅ So, Why Did Containers Win?
&lt;/h2&gt;

&lt;p&gt;Containers didn't win because of marketing or hype. They won because they solved real problems that were making our lives miserable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The "works on my machine" problem&lt;/li&gt;
&lt;li&gt;Slow deployment cycles&lt;/li&gt;
&lt;li&gt;Expensive, wasteful infrastructure&lt;/li&gt;
&lt;li&gt;Environment inconsistencies&lt;/li&gt;
&lt;li&gt;Scaling headaches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Are they perfect? Nope. But they're the right tool for most modern applications, and when you combine them with orchestration platforms, they become incredibly powerful.&lt;/p&gt;

&lt;p&gt;The smartest teams I know use containers where they shine, VMs where they must, and aren't religious about either one. It's all about picking the right tool for the job.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What's your experience with containers vs VMs? Any war stories or "aha" moments? I'd love to hear about them!&lt;/em&gt; 🤘&lt;/p&gt;

</description>
      <category>containers</category>
      <category>docker</category>
      <category>devops</category>
      <category>virtualmachine</category>
    </item>
    <item>
      <title>The Ultimate Guide: Which AI Coding Model Should You Use</title>
      <dc:creator>Meir Gabay</dc:creator>
      <pubDate>Wed, 25 Jun 2025 05:48:28 +0000</pubDate>
      <link>https://forem.com/unfor19/the-ultimate-guide-which-ai-coding-model-should-you-use-1pio</link>
      <guid>https://forem.com/unfor19/the-ultimate-guide-which-ai-coding-model-should-you-use-1pio</guid>
      <description>&lt;p&gt;Ever wondered which AI model is &lt;em&gt;truly&lt;/em&gt; best for coding tasks? Spoiler alert: there’s no one‑size‑fits‑all answer. Instead, I switch between top‑tier models depending on the task. Here’s my go‑to lineup and why it works wonders.&lt;/p&gt;

&lt;p&gt;I'll use &lt;a href="https://www.cursor.com/" rel="noopener noreferrer"&gt;Cursor IDE&lt;/a&gt; as an example, but the same principles apply to other AI coding tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ My Model Workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  💎 &lt;code&gt;gemini-2.5-pro&lt;/code&gt; – My Default All‑Rounder
&lt;/h3&gt;

&lt;p&gt;I rely on &lt;a href="https://deepmind.google/models/gemini/pro/" rel="noopener noreferrer"&gt;Gemini&lt;/a&gt; for most tasks: planning, execution, and drafting docs. It’s my everyday workhorse-fast, insightful, and reliable.&lt;/p&gt;

&lt;h3&gt;
  
  
  🎨 &lt;code&gt;claude-4-sonnet (thinking)&lt;/code&gt; – For Beautiful UI/UX
&lt;/h3&gt;

&lt;p&gt;When it's time to build or revamp the frontend, I switch to &lt;a href="https://www.anthropic.com/news/claude-4" rel="noopener noreferrer"&gt;Claude‑4‑Sonnet&lt;/a&gt;. Its styling finesse and design‑savvy code make it perfect for initial setup or feature‑rich interfaces. Since it costs about double Gemini, I quickly return to Gemini after the heavy lifting is done.&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%2F9j98eklxz797zz7ndioz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9j98eklxz797zz7ndioz.png" alt="claude-sonnet-4-thinking-requests.png" width="400" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🚧 &lt;code&gt;o3&lt;/code&gt; – The Breakthrough Model
&lt;/h3&gt;

&lt;p&gt;When I hit a wall-bugs or logic loops that seem unsolvable-&lt;code&gt;o3&lt;/code&gt; steps in. &lt;a href="https://community.openai.com/t/o3-is-80-cheaper-and-introducing-o3-pro/1284925" rel="noopener noreferrer"&gt;Recent price cuts&lt;/a&gt; make it more affordable, but its brevity means I only use it for major obstacles. Once the issue's cracked, it's back to Gemini for full implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  📋 Gemini + Claude Combo – For Clean Execution
&lt;/h3&gt;

&lt;p&gt;For large projects, my workflow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Plan&lt;/strong&gt; with Gemini &lt;code&gt;gemini-2.5-pro&lt;/code&gt; → generate structured Markdown breakdowns
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute&lt;/strong&gt; with Claude‑4‑Sonnet &lt;code&gt;claude-4-sonnet&lt;/code&gt; (standard mode)
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This duo gives me precise, well‑organized results without the “thinking noise” of Gemini. Claude follows the plan faithfully.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✍️ &lt;code&gt;gpt-4.1&lt;/code&gt; – For Polished Human Docs
&lt;/h3&gt;

&lt;p&gt;Finally, for rewriting documentation, I call in GPT‑4.1. Gemini goes over the code and drafts the docs according to logic; then I prompt GPT‑4.1 to make them &lt;em&gt;natural, human‑friendly, state‑of‑the‑art&lt;/em&gt;. Its English style simply outshines the rest, probably because I'm used to interacting with ChatGPT in the last couple of years.&lt;/p&gt;

&lt;h2&gt;
  
  
  📌 TL;DR: Which LLM for Which Task
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Preferred Model(s)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Everyday coding &amp;amp; planning&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;gemini‑2.5‑pro&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend UI/UX setup&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;claude‑4‑sonnet (thinking)&lt;/strong&gt; then back to Gemini&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Breaking through tough bugs&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;o3&lt;/strong&gt;, then back to Gemini&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large plan + structured build&lt;/td&gt;
&lt;td&gt;Gemini + &lt;strong&gt;claude‑4‑sonnet (standard)&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Natural‑language doc polishing&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;gpt‑4.1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  ✅ Why This Setup Works for Me
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Efficiency&lt;/strong&gt;: I use Gemini as my stable foundation for most work.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Specialization&lt;/strong&gt;: I deploy other models only where they excel-Claude for styling, o3 for breakthroughs, GPT‑4.1 for writing polish.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost control&lt;/strong&gt;: I minimize expensive model usage to specific tasks where they add the most value.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💡 Final Thought
&lt;/h2&gt;

&lt;p&gt;In Cursor, the smartest model isn’t always the most powerful one-it’s the &lt;em&gt;right tool for the task&lt;/em&gt;. Tailoring your workflow across these LLMs gives you speed, quality, and cost‑effectiveness. 😉&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Feel free to let me know if you'd like me to add sample Cursor commands or prompts!&lt;/em&gt;  &lt;/p&gt;

</description>
      <category>ai</category>
      <category>cursor</category>
      <category>productivity</category>
      <category>development</category>
    </item>
    <item>
      <title>How did the company you work for adopt ChatGPT?</title>
      <dc:creator>Meir Gabay</dc:creator>
      <pubDate>Tue, 15 Aug 2023 08:35:34 +0000</pubDate>
      <link>https://forem.com/unfor19/how-the-company-you-work-for-adopted-chatgpt-2cj3</link>
      <guid>https://forem.com/unfor19/how-the-company-you-work-for-adopted-chatgpt-2cj3</guid>
      <description>&lt;p&gt;The new AI tool &lt;a href="https://chat.openai.com/"&gt;ChatGPT&lt;/a&gt; is out there, though with such a powerful tool comes great responsibility.&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5eqpcaplijt9z7ug1nit.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5eqpcaplijt9z7ug1nit.jpeg" alt="Trulli" width="600" height="453"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;b&gt;Uncle Ben to Spiderman&lt;/b&gt;



&lt;p&gt;The main challenge is for companies and corporations that don't want secrets, passwords, codebase, Physical Health Indicator (PHI), medical data, or any other information to be leaked to OpenAI's ChatGPT.&lt;/p&gt;

&lt;p&gt;The web is full of articles and blog posts about the challenges, google for - &lt;a href="https://letmegooglethat.com/?q=chatgpt+regulation+challenge"&gt;ChatGPT regulation challenge&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;How does the company/organization/corporation you work for adopt ChatGPT? Do they block access to &lt;code&gt;chat.openai.com&lt;/code&gt;? Have you been instructed not to use it at all? Are you aware of the risks of copy-pasting organization data when asking ChatGPT questions?&lt;/p&gt;

&lt;p&gt;I would love to hear your thoughts and discuss this topic, fire at will.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>chatgpt</category>
      <category>ai</category>
    </item>
    <item>
      <title>Writing Bash Scripts Like A Pro - Part 2 - Error Handling</title>
      <dc:creator>Meir Gabay</dc:creator>
      <pubDate>Sat, 29 Jul 2023 22:02:19 +0000</pubDate>
      <link>https://forem.com/unfor19/writing-bash-scripts-like-a-pro-part-2-error-handling-46ff</link>
      <guid>https://forem.com/unfor19/writing-bash-scripts-like-a-pro-part-2-error-handling-46ff</guid>
      <description>&lt;p&gt;In the previous &lt;a href="https://dev.to/unfor19/writing-bash-scripts-like-a-pro-part-1-styling-guide-4bin"&gt;blog post&lt;/a&gt; of this series, we covered the basics of how to write a proper &lt;a href="https://www.gnu.org/software/bash/"&gt;Bash&lt;/a&gt; script, with a consistent naming convention. Writing Bash scripts consistently while learning new terms can be a significant challenge when doing it from scratch. But fear not! We're here to guide you on becoming a Bash scripting master!&lt;/p&gt;

&lt;p&gt;And the journey of loving Bash continues!&lt;/p&gt;

&lt;h2&gt;
  
  
  Definition of Done
&lt;/h2&gt;

&lt;p&gt;Before diving into the fascinating error-handling world in Bash scripts, let's set the "definition of done." By the end of this blog post, we aim to achieve the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get familiar with &lt;a href="https://tldp.org/LDP/abs/html/exitcodes.html"&gt;Bash's exit codes&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Take advantage of &lt;a href="https://tldp.org/HOWTO/Bash-Prog-Intro-HOWTO-3.html"&gt;STDERR and STDOUT&lt;/a&gt; to &lt;a href="https://www.cyberciti.biz/faq/redirecting-stderr-to-stdout/"&gt;handle errors&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Allow script execution to continue even if there's an error.&lt;/li&gt;
&lt;li&gt;Invoke an error handler (&lt;a href="https://tldp.org/HOWTO/Bash-Prog-Intro-HOWTO-8.html"&gt;function&lt;/a&gt;) according to an error message.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Handling Errors Like a Pro: Understanding Exit Codes
&lt;/h2&gt;

&lt;p&gt;Bash scripts return an exit status or exit code after execution. An exit code 0 means success, while a non-zero exit code indicates an error. Understanding exit codes is fundamental to effective error handling.&lt;/p&gt;

&lt;p&gt;When a command succeeds, it returns an exit code of 0, indicating success:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="c"&gt;# Print the exit code of the last command&lt;/span&gt;

&lt;span class="c"&gt;# Exit code 0 indicates success&lt;/span&gt;
&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;/path/to/home/dir
0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the &lt;a href="https://tldp.org/LDP/abs/html/basic.html"&gt;ls&lt;/a&gt; command fails, it returns a non-zero exit code, indicating an error:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="nb"&gt;ls&lt;/span&gt; /path/to/non-existent/directory
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="c"&gt;# Print the exit code of the last command&lt;/span&gt;

&lt;span class="c"&gt;# Exit code non-zero indicates an error&lt;/span&gt;
&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;ls: /path/to/non-existent/directory: No such file or directory
1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using Exit Codes to Our Advantage
&lt;/h2&gt;

&lt;p&gt;One way to handle errors is by adding the &lt;strong&gt;set -e&lt;/strong&gt;  option to your Bash scripts. When enabled, it ensures that the script &lt;strong&gt;will terminate immediately if any command exits with a non-zero status&lt;/strong&gt;. It's like a safety net that automatically catches errors and stops the script from continuing.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c"&gt;# Stop execution on any error&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"This line will be printed."&lt;/span&gt;

&lt;span class="c"&gt;# Simulate an error&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; /nonexistent-directory

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"This line will NOT be printed."&lt;/span&gt;
&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;This line will be printed.
ls: /nonexistent-directory: No such file or directory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the &lt;code&gt;ls&lt;/code&gt; command attempts to list the contents of a non-existent directory, causing an error. Due to &lt;code&gt;set -e&lt;/code&gt;, the script will stop executing after encountering the error, and the last line won't be printed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap on new terms
&lt;/h2&gt;

&lt;p&gt;We covered a few new characters and terms, so let's make sure we fully understand what they do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html"&gt;$HOME&lt;/a&gt; variable exists on any &lt;a href="https://en.wikipedia.org/wiki/POSIX"&gt;POSIX&lt;/a&gt; system, so I used it to demonstrate how Bash can use a &lt;a href="https://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html"&gt;Global Environment Variable&lt;/a&gt; that contains your "home directory path".&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://tldp.org/LDP/abs/html/special-chars.html"&gt;$?&lt;/a&gt; character is an &lt;em&gt;exit status variable&lt;/em&gt; which stores the exit code of the previous command.&lt;/li&gt;
&lt;li&gt;The option &lt;a href="https://tldp.org/LDP/abs/html/options.html#:~:text=errexit"&gt;set -e&lt;/a&gt; forces the script to stop executing when encountering any error.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Redirecting STDERR and STDOUT: Capturing Errors
&lt;/h2&gt;

&lt;p&gt;Often, you might want to capture the output (both &lt;a href="https://tldp.org/HOWTO/Bash-Prog-Intro-HOWTO-3.html"&gt;STDOUT and STDERR&lt;/a&gt;) of a command and handle it differently based on whether it succeeded or failed (raised an error).&lt;/p&gt;

&lt;p&gt;We can use the expression &lt;a href="https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Command-Substitution"&gt;$(subcommand)&lt;/a&gt; to execute a command and then capture its output into a variable. The important part is to redirect &lt;code&gt;STDERR&lt;/code&gt; to &lt;code&gt;STDOUT&lt;/code&gt; by adding &lt;code&gt;2&amp;gt;&amp;amp;1&lt;/code&gt; to the end of the "subcommand".&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c"&gt;# Run the 'ls' command and redirect both STDOUT and STDERR to the 'response' variable&lt;/span&gt;
&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /nonexistent-directory 2&amp;gt;&amp;amp;1&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="c"&gt;# Did NOT set `set -e`, hence script continues even if there's an error&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Success: &lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: &lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&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;Error: ls: /nonexistent-directory: No such file or directory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the &lt;code&gt;ls&lt;/code&gt; command attempts to list the contents of a non-existent directory. The output (including the error message) is captured in the response variable. We then check the exit status using &lt;code&gt;$?&lt;/code&gt; and print either &lt;code&gt;"Success: $response"&lt;/code&gt; or &lt;code&gt;"Error: $response"&lt;/code&gt; accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Allowing Script Execution Despite Errors
&lt;/h2&gt;

&lt;p&gt;So far, we covered &lt;code&gt;set -e&lt;/code&gt; to terminate execution on an error and redirect error output to standard output &lt;code&gt;2&amp;gt;&amp;amp;1&lt;/code&gt;, but what happens if we combine them?&lt;/p&gt;

&lt;p&gt;You may want to continue executing the script even if a command fails and save the error message for later use in an error handler. The &lt;code&gt;|| true&lt;/code&gt; technique comes to the rescue! It allows the script to proceed without terminating, even if a command exits with a non-zero status. That is an excellent technique for handling errors according to their content.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ping Servers Scenario
&lt;/h3&gt;

&lt;p&gt;In the following example, we attempt to ping each server, with a timeout of 1 second, using the &lt;a href="https://tldp.org/HOWTO/Ethernet-Bridge-netfilter-HOWTO-4.html"&gt;ping&lt;/a&gt; command. If the server is reachable, we should print "Response - ${response}.". Though what happens if the ping fails? How do we handle that? Let's solve it with a use-case scenario!&lt;/p&gt;

&lt;p&gt;To make it authentic as possible, I added an &lt;a href="https://tldp.org/LDP/abs/html/arrays.html#:~:text=empty%20arrays%20and%20empty%20elements"&gt;array&lt;/a&gt; of servers with the &lt;code&gt;variable_name=()&lt;/code&gt; expression.&lt;/p&gt;

&lt;p&gt;After that, I used the &lt;a href="https://tldp.org/HOWTO/Bash-Prog-Intro-HOWTO-7.html"&gt;for do; something; done&lt;/a&gt; loop to iterate over the servers. For each iteration, we ping a server and redirect &lt;code&gt;STDERR&lt;/code&gt; to &lt;code&gt;STDOUT&lt;/code&gt; to capture the error's output to the variable &lt;code&gt;response&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And the final tweak was to add &lt;code&gt;|| true&lt;/code&gt; inside the &lt;code&gt;$()&lt;/code&gt; evaluation so that even if the &lt;code&gt;ping&lt;/code&gt; command fails, its output is saved in the &lt;code&gt;response&lt;/code&gt; variable.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c"&gt;# PARTIAL SOLUTION - do not copy paste&lt;/span&gt;

&lt;span class="c"&gt;# Stop execution on any error&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;


&lt;span class="c"&gt;# Creates an array, values are delimited by spaces&lt;/span&gt;
&lt;span class="nv"&gt;servers&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s2"&gt;"google.com"&lt;/span&gt; &lt;span class="s2"&gt;"netflix.com"&lt;/span&gt; &lt;span class="s2"&gt;"localhost:1234"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;for &lt;/span&gt;item &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;servers&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c"&gt;# Use the 'ping' command and redirect both STDOUT and STDERR to the 'response' variable&lt;/span&gt;
    &lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ping &lt;span class="nt"&gt;-c&lt;/span&gt; 1 &lt;span class="nt"&gt;-t&lt;/span&gt; 1 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;&amp;amp;1 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="c"&gt;# TODO: Fix, always evaluates as true&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"
Response for &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:
--------------------------------------------------------------
&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
--------------------------------------------------------------"&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error - &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi
done&lt;/span&gt;
&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;Response for google.com:
--------------------------------------------------------------
PING google.com (172.217.22.14): 56 data bytes
64 bytes from 172.217.22.14: icmp_seq=0 ttl=56 time=126.156 ms

--- google.com ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 126.156/126.156/126.156/0.000 ms
--------------------------------------------------------------

Response for netflix.com: &amp;lt;------- Sholuld've been Error not Response
--------------------------------------------------------------
PING netflix.com (3.251.50.149): 56 data bytes

--- netflix.com ping statistics ---
1 packets transmitted, 0 packets received, 100.0% packet loss
--------------------------------------------------------------

Response for localhost:1234: &amp;lt;------- Sholuld've been Error not Response
--------------------------------------------------------------
ping: cannot resolve localhost:1234: Unknown host
--------------------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response will always be evaluated as &lt;code&gt;true&lt;/code&gt; because of this part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ...&lt;/span&gt;
    &lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ping &lt;span class="nt"&gt;-c&lt;/span&gt; 1 &lt;span class="nt"&gt;-t&lt;/span&gt; 1 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;&amp;amp;1 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="c"&gt;# &amp;lt;-- At this point, `$?` is always `0` because of `|| true`&lt;/span&gt;

    &lt;span class="c"&gt;# Will constantly evaluate as `true`, 0 = 0&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The best way to fix it is to analyze a successful response message and set it as an "indicator of a successful response", and in any other case, the script should fail with an error message.&lt;/p&gt;

&lt;p&gt;In the case of running &lt;code&gt;ping -c 1 -t1 $item&lt;/code&gt;, a successful response can be considered as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Good response, according to a tested output&lt;/span&gt;
1 packets transmitted, 1 packets received, 0.0% packet loss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The analysis should be done for a specific use case; this approach assists with handling unknown errors by setting a single source of truth for a successful response and considering anything else as an error.&lt;/p&gt;

&lt;p&gt;Here's the final version of the code, with a few upgrades to the output:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c"&gt;# Good example&lt;/span&gt;

&lt;span class="c"&gt;# Stop execution on any error&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;


&lt;span class="nv"&gt;servers&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s2"&gt;"google.com"&lt;/span&gt; &lt;span class="s2"&gt;"netflix.com"&lt;/span&gt; &lt;span class="s2"&gt;"localhost:1234"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;for &lt;/span&gt;item &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;servers&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c"&gt;# Use the 'ping' command and redirect both STDOUT and STDERR to the 'response' variable&lt;/span&gt;
    &lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ping &lt;span class="nt"&gt;-c&lt;/span&gt; 1 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; 1 2&amp;gt;&amp;amp;1 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;# The condition is based on what we consider a successful response&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"1 packets transmitted, 1 packets received, 0.0% packet loss"&lt;/span&gt; 1&amp;gt;/dev/null 2&amp;gt;/dev/null &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"
SUCCESS :: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
--------------------------------------------------------------
&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
--------------------------------------------------------------"&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"
ERROR :: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
--------------------------------------------------------------
&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
--------------------------------------------------------------"&lt;/span&gt;
    &lt;span class="k"&gt;fi
done&lt;/span&gt;
&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;SUCCESS :: google.com
--------------------------------------------------------------
PING google.com (172.217.22.14): 56 data bytes
64 bytes from 172.217.22.14: icmp_seq=0 ttl=56 time=159.311 ms

--- google.com ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 159.311/159.311/159.311/0.000 ms
--------------------------------------------------------------

ERROR :: netflix.com
--------------------------------------------------------------
PING netflix.com (54.246.79.9): 56 data bytes

--- netflix.com ping statistics ---
1 packets transmitted, 0 packets received, 100.0% packet loss
--------------------------------------------------------------

ERROR :: localhost:1234
--------------------------------------------------------------
ping: cannot resolve localhost:1234: Unknown host
--------------------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What the grep?
&lt;/h3&gt;

&lt;p&gt;You've just learned how to use a &lt;a href="https://tldp.org/HOWTO/Bash-Prog-Intro-HOWTO-4.html"&gt;Bash pipe |&lt;/a&gt; to pass data to the &lt;a href="https://tldp.org/LDP/Bash-Beginners-Guide/html/sect_04_02.html"&gt;grep command&lt;/a&gt;. The trick is to &lt;code&gt;echo ${a_variable}&lt;/code&gt; and pipe it with &lt;code&gt;|&lt;/code&gt; to the &lt;code&gt;grep&lt;/code&gt; command like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"some response"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"some"&lt;/span&gt; 1&amp;gt;/dev/null 2&amp;gt;/dev/null &lt;span class="c"&gt;# Success&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"not-in-text"&lt;/span&gt; 1&amp;gt;/dev/null 2&amp;gt;/dev/null &lt;span class="c"&gt;# Fail&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
&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;0
1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You've also learned about &lt;a href="https://tldp.org/LDP/abs/html/zeros.html"&gt;/dev/null&lt;/a&gt;, a black hole where you can redirect output that shouldn't be printed or saved anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Custom Error Handlers
&lt;/h2&gt;

&lt;p&gt;A more complex scenario may require dedicated error handlers, for example, executing an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods"&gt;HTTP Request&lt;/a&gt; with &lt;a href="https://curl.se/"&gt;curl&lt;/a&gt;, and handling &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Response"&gt;HTTP Responses&lt;/a&gt;; You can create a custom function to handle specific responses gracefully, like this:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c"&gt;# Stop execution on any error&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;


handle_api_error&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nv"&gt;$msg&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="s1"&gt;'{"message":"Not Found","code":404}'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# In case of page not found&lt;/span&gt;
            &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: Resource not found!"&lt;/span&gt;
            &lt;span class="nb"&gt;exit &lt;/span&gt;4 &lt;span class="c"&gt;# Bash exit code&lt;/span&gt;
            &lt;span class="p"&gt;;;&lt;/span&gt;
        &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# Any other case&lt;/span&gt;
            &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: Unknown API error with message &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;
            &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;esac&lt;/span&gt;

    &lt;span class="c"&gt;# Exit either way&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="c"&gt;# Make a request to the API and store the response text in the 'response' variable&lt;/span&gt;
&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://catfact.ninja/fact 2&amp;gt;&amp;amp;1 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;


&lt;span class="c"&gt;# According to a successful response, having `"fact":` is a good indicator&lt;/span&gt;
&lt;span class="c"&gt;# If `"fact"` does not `!` appear in the message, it's an error&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;fact&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:"&lt;/span&gt; 1&amp;gt;/dev/null &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;handle_api_error &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# At this point, we are sure the response is valid&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Response: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# For this specific API, a successful response returns a random fact about cats&lt;/span&gt;
Response: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"fact"&lt;/span&gt;:&lt;span class="s2"&gt;"Neutering a male cat will, in almost all cases, stop him from spraying (territorial marking), fighting with other males (at least over females), as well as lengthen his life and improve its quality."&lt;/span&gt;,&lt;span class="s2"&gt;"length"&lt;/span&gt;:198&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;This blog post took it up a notch; you've learned several terms and can now handle errors in Bash like a Pro! There are still more tricks in this error-handling mix, like &lt;a href="https://rimuhosting.com/knowledgebase/linux/misc/trapping-ctrl-c-in-bash"&gt;trapping CTRL-C error&lt;/a&gt;; we'll discover more about that and other ways to handle errors using Bash scripts.&lt;/p&gt;

</description>
      <category>bash</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Create Multiple Static Sites In A Single AWS S3 Bucket Served By AWS CloudFront</title>
      <dc:creator>Meir Gabay</dc:creator>
      <pubDate>Wed, 07 Sep 2022 01:34:55 +0000</pubDate>
      <link>https://forem.com/unfor19/multiple-static-sites-in-a-single-s3-bucket-served-by-cloudfront-3ako</link>
      <guid>https://forem.com/unfor19/multiple-static-sites-in-a-single-s3-bucket-served-by-cloudfront-3ako</guid>
      <description>&lt;p&gt;In this blog post, I'll share how to host multiple static websites in a single S3 bucket, where each site is stored in a single directory (path).&lt;/p&gt;

&lt;p&gt;CloudFront will serve the static website, and the trick to fetch the relevant site from the origin, S3 bucket, in this case, relies on a Lambda@Edge function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Objectives
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Serve safely behind a &lt;a href="https://aws.amazon.com/cloudfront/" rel="noopener noreferrer"&gt;CDN&lt;/a&gt; via &lt;a href="https://aws.amazon.com/certificate-manager/" rel="noopener noreferrer"&gt;HTTPS&lt;/a&gt; - &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cnames-and-https-requirements.html" rel="noopener noreferrer"&gt;CloudFront + ACM&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;Accessing the S3 bucket should be forbidden to anyone except for the serving CloudFront distribution; we'll use &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html" rel="noopener noreferrer"&gt;CloudFront OAI&lt;/a&gt; for that&lt;/li&gt;
&lt;li&gt;Store in durable storage, &lt;a href="https://aws.amazon.com/s3/faqs/#:~:text=What%20is%20S3%20Standard?" rel="noopener noreferrer"&gt;S3 Standard Durability 99.999999999% (11 9's)&lt;/a&gt;, and highly available servers, &lt;a href="https://aws.amazon.com/s3/faqs/#:~:text=What%20is%20S3%20Standard?" rel="noopener noreferrer"&gt;S3 Standard Availability 99.99%&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of this blog post, I expect to have two live websites, which are stored on a single S3 bucket, and I want proof.&lt;/p&gt;

&lt;p&gt;And of course, I'm too excited to keep to myself, right now I'm inspired by the below song ... So ... Are you re-e-e-e-ady let's go!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=xxIsmbVZuSI&amp;amp;ab_channel=EscapetheFate" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvuoeokqflkqsr03wpz9.png" alt="are-you-ready-lets-go"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=xxIsmbVZuSI&amp;amp;ab_channel=EscapetheFate" rel="noopener noreferrer"&gt;Escape the Fate - "One For The Money"&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  (1/6) HTTPS And DNS Validation
&lt;/h2&gt;

&lt;p&gt;This step is optional if you already own an eligible ACM Certificate in the AWS region &lt;strong&gt;N.Virginia (us-east-1)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I own the domain &lt;a href="https://meirg.co.il" rel="noopener noreferrer"&gt;meirg.co.il&lt;/a&gt;, so to avoid breaking my site, I'm adding another subdomain, and I'll name it &lt;code&gt;multiple-sites&lt;/code&gt;, which makes my &lt;a href="https://en.wikipedia.org/wiki/Fully_qualified_domain_name" rel="noopener noreferrer"&gt;FQDN&lt;/a&gt;&lt;/p&gt;

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

multiple-sites.meirg.co.il


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

&lt;/div&gt;

&lt;p&gt;Additionally, I want to support accessing the site with &lt;code&gt;www&lt;/code&gt; or any other subdomain (maybe &lt;code&gt;api&lt;/code&gt;), so I'll add another FQDN&lt;/p&gt;

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

*.multiple-sites.meirg.co.il


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Create An AWS ACM Certificate
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F67lpt520spvvbo7fhrvt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F67lpt520spvvbo7fhrvt.png" alt="create-acm-certificate"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Create DNS Validation Records In DNS
&lt;/h3&gt;

&lt;p&gt;Now I need to validate the certificate in my DNS management provider; in my case, it's &lt;a href="https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-dns-records/" rel="noopener noreferrer"&gt;Cloudflare&lt;/a&gt; (flare, not Front 😉)&lt;/p&gt;

&lt;p&gt;Then, I copied the CNAME+Value that was generated in &lt;a href="https://docs.aws.amazon.com/acm/latest/userguide/dns-validation.html" rel="noopener noreferrer"&gt;ACM&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;And then, I created two new DNS records in my DNS management provider (&lt;a href="https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/migrate-dns-domain-in-use.html" rel="noopener noreferrer"&gt;how to do it in AWS Route53&lt;/a&gt;). Here's how it looks like in Cloudflare:&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: &lt;a href="https://www.cloudflare.com/cdn/" rel="noopener noreferrer"&gt;Cloudflare provides a native CDN&lt;/a&gt; solution per DNS address, which is fantastic. As you can see, I turned it off (DNS Only) since it's irrelevant for the DNS validation.&lt;/p&gt;

&lt;p&gt;And then ... I wonder if all those "and then" reminded you &lt;a href="https://youtu.be/geUYoyuETBI?t=308" rel="noopener noreferrer"&gt;Dobby from the Harry Potter movie&lt;/a&gt; 🤭&lt;/p&gt;
&lt;h3&gt;
  
  
  Cloudflare And AWS CAA Issue
&lt;/h3&gt;

&lt;p&gt;I got into big trouble; my certificate failed to validate&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgp52puz4b60tb2htyhjy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgp52puz4b60tb2htyhjy.png" alt="acm-failed-certificates"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even though this part is not directly related to this blog post, I'm putting it here for future me (or the currently frustrated you who found it). Bottom line, I fixed it by reading &lt;a href="https://docs.aws.amazon.com/acm/latest/userguide/setup-caa.html" rel="noopener noreferrer"&gt;How to Configure a CAA record for AWS&lt;/a&gt; and created the DNS records accordingly in Cloudflare.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flm6t5tv3k8akzphuedcy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flm6t5tv3k8akzphuedcy.png" alt="cloudflare-caa-records"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Validated ACM
&lt;/h3&gt;

&lt;p&gt;Finally, my ACM certificate is eligible, and all DNS records were validated successfully, thanks to the CAA records created above 🙇🏻‍♂️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzrsq4zpkrugqs2clgsie.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzrsq4zpkrugqs2clgsie.png" alt="acm-validated-certificates"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  (2/6) Create An S3 Bucket
&lt;/h2&gt;

&lt;p&gt;This part is relatively easy if you're familiar with AWS S3, though I do want to emphasize something -&lt;/p&gt;

&lt;p&gt;An AWS S3 bucket is an &lt;a href="https://cloud.google.com/learn/what-is-object-storage" rel="noopener noreferrer"&gt;object storage&lt;/a&gt;; hence it cannot be served to a browser unless set to &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteHosting.html" rel="noopener noreferrer"&gt;AWS S3 Static Website&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: S3 is an object storage, so &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-folders.html" rel="noopener noreferrer"&gt;directories (paths) aren't really "storage locations"&lt;/a&gt;, but more like pointers that state where the data is stored in an organized way, like filesystem storage.&lt;/p&gt;

&lt;p&gt;An S3 static website is only available via HTTP. Serving the site via HTTPS requires a CDN, such as AWS CloudFront or Cloudflare. This information is available at &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/cloudfront-serve-static-website/" rel="noopener noreferrer"&gt;How do I use CloudFront to serve a static website hosted on Amazon S3?&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So why am I double-posting something that is already out there? Because I found it very confusing to realize that the term "static website" and the term "S3 static website option" are different.&lt;/p&gt;

&lt;p&gt;By its name, I would expect to enable &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/HostingWebsiteOnS3Setup.html" rel="noopener noreferrer"&gt;AWS S3 Static Website Option&lt;/a&gt; to make this solution work. It should be &lt;strong&gt;disabled&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's the thing, AWS S3 Static Website doesn't support &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html" rel="noopener noreferrer"&gt;CloudFront Origin Access Identity (OAI)&lt;/a&gt;. As mentioned in the &lt;em&gt;Objectives&lt;/em&gt;, the site should be accessed from CloudFront only, which means I need the &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/cloudfront-access-to-amazon-s3/" rel="noopener noreferrer"&gt;OAI&lt;/a&gt; functionality.&lt;/p&gt;
&lt;h3&gt;
  
  
  Recap - AWS Static Website Option
&lt;/h3&gt;

&lt;p&gt;AWS Static Website Option should be &lt;strong&gt;disabled&lt;/strong&gt; when served behind a &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-working-with.html" rel="noopener noreferrer"&gt;CloudFront distribution&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  CloudFront Invalidation
&lt;/h3&gt;

&lt;p&gt;A &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html" rel="noopener noreferrer"&gt;CloudFront invalidation&lt;/a&gt; is the part where you purge the cache after pushing static content HTML/CSS/JS/etc., to the S3 bucket.&lt;/p&gt;

&lt;p&gt;Later on, when I push new content, I will invalidate the cache to get immediate results. It's either that or setting the &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html" rel="noopener noreferrer"&gt;CloudFront's distribution TTL&lt;/a&gt; to a lower number than the default &lt;code&gt;86400 seconds&lt;/code&gt; (24 hours).&lt;/p&gt;
&lt;h3&gt;
  
  
  Create a bucket
&lt;/h3&gt;

&lt;p&gt;The name doesn't matter as long as it's &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html" rel="noopener noreferrer"&gt;a valid S3 bucket name&lt;/a&gt; and that &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingBucket.html" rel="noopener noreferrer"&gt;name is globally available across ALL AWS accounts&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;As you can see, I chose a logical name for my bucket (luckily, it was available)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

multiple-sites.meirg.co.il


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

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;IMPORTANT&lt;/strong&gt;: The S3 bucket name doesn't have to be the same as your DNS records. If the name is taken, add a suffix like &lt;code&gt;-site&lt;/code&gt; or any other logical suffix that would make sense when you search for the S3 bucket by name. In this solution, The S3 bucket is served behind a CDN (CloudFront), so its name doesn't matter.&lt;/p&gt;

&lt;p&gt;Scroll down, keep the &lt;strong&gt;Block all public access&lt;/strong&gt; ticked, and set Versioning to &lt;strong&gt;Enabled&lt;/strong&gt;.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  (3/6) Push Website Content To S3
&lt;/h2&gt;

&lt;p&gt;By "push", I mean upload; I use the verb "push" because I want to keep in mind that an automated CI/CD process should "push" the content instead of "uploading" it manually via S3 like I'm about to do now.&lt;/p&gt;

&lt;p&gt;Following the bucket creation, I've created two directories (folders); as promised in the beginning, each directory will serve a different website.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft3wvr8ldusv84s5aighr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft3wvr8ldusv84s5aighr.png" alt="s3-bucket-push-content-1-directories"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then (Dobby again), I've created this dummy &lt;code&gt;index.html&lt;/code&gt; file and made two copies, so I can differentiate between the sites when I check them in my browser.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;site1/index.html&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Welcome to site&lt;span class="nt"&gt;&amp;lt;u&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/u&amp;gt;&lt;/span&gt;!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;site2/index.html&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Welcome to site&lt;span class="nt"&gt;&amp;lt;u&amp;gt;&lt;/span&gt;2&lt;span class="nt"&gt;&amp;lt;/u&amp;gt;&lt;/span&gt;!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Moving on to the tricky parts - &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-creating-console.html" rel="noopener noreferrer"&gt;Creating a CloudFront distribution&lt;/a&gt; and &lt;a href="https://aws.amazon.com/lambda/edge/" rel="noopener noreferrer"&gt;Lambda@Edge&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  (4/6) Create A CloudFront Distribution
&lt;/h2&gt;

&lt;p&gt;As part of this step, we'll also create (automatically) an Origin Access Identity, enabling access for CloudFront to the S3 bucket; Currently, none is allowed to view the websites.&lt;/p&gt;

&lt;p&gt;Once you choose the CloudFront service, you're redirected to the "Global" region, don't let it fool you; it's considered as N.Virginia (EU-east-1), creating the ACM certificate in N.Virginia is essential, as it's the same region where CloudFront is hosted.&lt;/p&gt;

&lt;p&gt;The story continues; I've created a CloudFront distribution and searched for my bucket &lt;code&gt;multiple-sites.meirg.co.il&lt;/code&gt; as the Origin. The name of the origin was automatically changed in the GUI to &lt;code&gt;multiple-sites.meirg.co.il.s3.eu-west-1.amazonaws.com&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;To create an OAI, I've clicked the &lt;strong&gt;Create control setting&lt;/strong&gt; button, and after reading &lt;a href="https://dev.toAdvanced%20settings%20for%20origin%20access%20control"&gt;Advanced settings for origin access control&lt;/a&gt;, I chose &lt;strong&gt;Always sign origin requests (recommended setting)&lt;/strong&gt; since my bucket is &lt;strong&gt;private&lt;/strong&gt; (future to be accessible only via CloudFront).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: I also realized that OAI had become legacy, and I should start using the Access Control Setting feature instead.&lt;/p&gt;

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

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

&lt;p&gt;For cache key and origin requests, I've selected &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-origin-request-policies.html" rel="noopener noreferrer"&gt;CORS-S3Origin&lt;/a&gt;, which is a must; otherwise, you'll get a 502 error due to failure of signing requests (try it).&lt;/p&gt;

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

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

&lt;p&gt;I was surprised by the excellent UX (not kidding); After creating the CloudFront distribution, I got the following pop-up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3ftlu8u2iac7iurt9h9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3ftlu8u2iac7iurt9h9.png" alt="cloudfront-create-distribution-4-after-creation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As instructed, I clicked &lt;strong&gt;Copy policy&lt;/strong&gt; and the highlighted link &lt;strong&gt;Go to S3 bucket permissions to update policy&lt;/strong&gt;, scrolled down a bit to &lt;strong&gt;Bucket Policy&lt;/strong&gt;, and clicked &lt;strong&gt;Edit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this screen, I pasted the copied policy, and it got a bit messed up (no idea why), so I made sure the curly braces at the top &lt;code&gt;{&lt;/code&gt; and in the end &lt;code&gt;}&lt;/code&gt; are not indented at all, and then I was able to click &lt;strong&gt;Save Changes&lt;/strong&gt; (scroll to bottom) without an issue.&lt;/p&gt;

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

&lt;p&gt;Here's what it looks like after clicking the &lt;strong&gt;Save Changes&lt;/strong&gt; button.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Recap So Far
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Created an ACM certificate in N.Virginia (us-east-1) with eligible DNS records

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;multiple-sites.meirg.co.il&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;*.multiple-sites.meirg.co.il&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Created an S3 bucket in Ireland (EU-west-1) and named it &lt;code&gt;multiple-sites.meirg.co.il&lt;/code&gt; &lt;/li&gt;

&lt;li&gt;Pushed two websites to the S3 bucket

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;site1/index.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;site2/index.html&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Created a CloudFront distribution and updated the S3 bucket with the generated Access Control Bucket Policy&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  (5/6) Map DNS record to CloudFront
&lt;/h2&gt;

&lt;p&gt;For testing purposes, I've created another &lt;code&gt;index.html&lt;/code&gt; file; Remember that I set a &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DefaultRootObject.html" rel="noopener noreferrer"&gt;Default Root Object&lt;/a&gt; during the CloudFront distribution creation? That is it.&lt;/p&gt;

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

&amp;lt;h1&amp;gt;Welcome to root site!&amp;lt;/h1&amp;gt;


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

&lt;/div&gt;

&lt;p&gt;The new &lt;code&gt;index.html&lt;/code&gt; file is pushed to the root directory &lt;code&gt;/&lt;/code&gt; of the bucket; Before moving on to the "site per directory solution with Lambda@Edge" I want to make sure that everything is in place and that &lt;a href="https://multiple-sites.meirg.co.il" rel="noopener noreferrer"&gt;https://multiple-sites.meirg.co.il&lt;/a&gt; works as expected.&lt;/p&gt;

&lt;p&gt;So now my S3 bucket looks like this:&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;FQDN Served by CloudFront (Not Reached) - &lt;a href="https://multiple-sites.meirg.co.il" rel="noopener noreferrer"&gt;https://multiple-sites.meirg.co.il&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;S3 Direct Access Link (Forbidden) - &lt;a href="https://s3.eu-west-1.amazonaws.com/multiple-sites.meirg.co.il/index.html" rel="noopener noreferrer"&gt;https://s3.eu-west-1.amazonaws.com/multiple-sites.meirg.co.il/index.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The FQDN is still unavailable as I haven't added the DNS record in my DNS Management Provider (Cloudflare); As of now, none knows that my website &lt;code&gt;multiple-sites.meirg.co.il&lt;/code&gt; should point to the created CloudFront distribution.&lt;/p&gt;

&lt;p&gt;Copied the CNAME of the CloudFront distribution&lt;/p&gt;

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

&lt;p&gt;And created a new DNS record for that CloudFront distribution CNAME; Since I'm already using CloudFront as a CDN, I prefer to disable Cloudflare's proxy feature, as It'll be CDN to CDN, which sounds like a bad idea.&lt;/p&gt;

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

&lt;p&gt;Testing the root site &lt;a href="//multiple-sites.meirg.co.il"&gt;https://multiple-sites.meirg.co.il&lt;/a&gt; ...&lt;/p&gt;

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

&lt;p&gt;It worked 🥳 but that's not why we're here for! Moving on to the multiple directories magic. Why magic, you ask? The nested paths &lt;code&gt;/site1&lt;/code&gt;,&lt;code&gt;/site2&lt;/code&gt; are restricted/forbidden; here's how accessing &lt;code&gt;/site1&lt;/code&gt; looks like at the time of writing this blog post:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  (6/6) Create A Lambda@Edge Function
&lt;/h2&gt;

&lt;p&gt;Writing code is excellent; copying from someone else and understanding what it does is even better. After Googling a bit, I stumbled on &lt;a href="https://aws.amazon.com/blogs/compute/implementing-default-directory-indexes-in-amazon-s3-backed-amazon-cloudfront-origins-using-lambdaedge/" rel="noopener noreferrer"&gt;Implementing Default Directory Indexes in Amazon S3-backed Amazon CloudFront Origins Using Lambda@Edge&lt;br&gt;
&lt;/a&gt; contains the exact code that I need.&lt;/p&gt;

&lt;p&gt;I copied the snippet from the mentioned site and removed the &lt;code&gt;console.log&lt;/code&gt;; I'll add them back if I need to debug.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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="nx"&gt;callback&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;// Extract the request from the CloudFront event that is sent to Lambda@Edge &lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Records&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;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Extract the URI from the request&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;olduri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Match any '/' that occurs at the end of a URI. Replace it with a default index&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;newuri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;older&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;\/&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;/index.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Replace the received URI with the URI that includes the index page&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;URI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newuri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Return to CloudFront&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&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;
  
  
  How Does The Lambda@Edge Work?
&lt;/h3&gt;

&lt;p&gt;By default, if an origin request URI doesn't contain file path, like navigating &lt;code&gt;https://multiple-sites.meirg.co.il/site1/&lt;/code&gt;; An error will be raised since there's no default setting for sub-directories to use the hosted &lt;code&gt;site1/index.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So why does it work when I navigate the root path of the bucket &lt;code&gt;https://multiple-sites.meirg.co.il/&lt;/code&gt;? Because I've set the &lt;strong&gt;Default Root Object&lt;/strong&gt; to &lt;code&gt;index.html&lt;/code&gt; in the CloudFront distribution. That "fixes" the issue for a single website hosted at the root of an S3 bucket.&lt;/p&gt;

&lt;p&gt;When it comes to multiple websites stored in the same S3 bucket, we need a mediator/proxy that will convert the origin request URI from &lt;code&gt;/&lt;/code&gt; to &lt;code&gt;/index.html&lt;/code&gt;, which is precisely what the above-copied code snippet does.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create The Lambda@Edge Function
&lt;/h3&gt;

&lt;p&gt;Firstly, in which region should I create the Lambda@Edge Function? .... Can you guess? ......... N.Virginia (us-east-1)! Reminding you that all CloudFront-related resources need to be located in N.Virginia (us-east-1).&lt;/p&gt;

&lt;p&gt;Go ahead and create a Lambda Function in N.Virginia, and keep the default settings "Author from scratch &amp;gt; Node 16.x", reminding you that the Lambda Function's code snippet was in JavaScript.&lt;/p&gt;

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

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

&lt;p&gt;This step is mandatory as &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-edit-function.html" rel="noopener noreferrer"&gt;Lambda@Edge functions are referenced with versions&lt;/a&gt;, unlike normal Lambda Functions can be referenced using the &lt;code&gt;$LATEST&lt;/code&gt; alias.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyzki3k11zfn66vmj8naf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyzki3k11zfn66vmj8naf.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Deploy Lambda@Edge To The CloudFront Distribution
&lt;/h4&gt;

&lt;p&gt;Navigate back to the function and&lt;br&gt;
Deploy to Lambda@Edge!&lt;/p&gt;

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

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

&lt;p&gt;I clicked &lt;strong&gt;Deploy&lt;/strong&gt; (with excitement), and then I got this pop-up message:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Correct the errors above and try again.&lt;br&gt;
Your function's execution role must be assumable by the edgelambda.amazonaws.com service principal.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;Hold on a sec; I need to add a Trust Relationship, so CloudFront is allowed to execute the function. Googling a bit got me here - &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-permissions.html" rel="noopener noreferrer"&gt;Setting IAM permissions and roles for Lambda@Edge&lt;/a&gt;; the copying motive continues as I copied the IAM policy from that page&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&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;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="s2"&gt;"lambda.amazonaws.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="s2"&gt;"edgelambda.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


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

&lt;/div&gt;

&lt;p&gt;After that, I edited the Lambda Function's IAM role Trust Relationship.&lt;/p&gt;

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

&lt;p&gt;Let's try to deploy the Lambda Function again ... And of course, another error ...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cannot read properties of undefined (reading 'startsWith')&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;Oh well, I refreshed the Lambda Function's page ... I Tried to deploy again ... Finally, deployed! &lt;/p&gt;

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

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;I'm not sure if I did it or if it happened automatically. Still, I think it's important to document it - initially, I got to the 403 Forbidden page when trying to access &lt;a href="https://multiple-sites.meirg.co.il/site1/" rel="noopener noreferrer"&gt;https://multiple-sites.meirg.co.il/site1/&lt;/a&gt;, so I checked the CloudFront distribution and found out that the S3 origin is set to &lt;strong&gt;Public&lt;/strong&gt;, and I have no idea how or why that happened. So I changed it back to &lt;code&gt;Origin access control settings (recommended)&lt;/code&gt;, and everything works as expected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://multiple-sites.meirg.co.il/site1/" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2mgl38g8mhsafnzzc3tm.png" alt="site-1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://multiple-sites.meirg.co.il/site2/" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1o5om517aigelkzg6n8x.png" alt="site-2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;Initially, I wanted to use an Origin Access Identity (OAI) to restrict to CloudFront only. Then I discovered that OAI is considered legacy, so I proceeded with Access Control, and I'll probably &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#migrate-from-oai-to-oac" rel="noopener noreferrer"&gt;migrate all my OAI resources to Access Control&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I left out (for now) the "S3 redirect from www" part because this blog post is already overkilled.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How To Recover Secrets From GitHub Actions</title>
      <dc:creator>Meir Gabay</dc:creator>
      <pubDate>Fri, 01 Jul 2022 11:40:05 +0000</pubDate>
      <link>https://forem.com/unfor19/how-to-recover-secrets-from-github-actions-5k2</link>
      <guid>https://forem.com/unfor19/how-to-recover-secrets-from-github-actions-5k2</guid>
      <description>&lt;p&gt;In this blog post, I'll share how to recover a secret from a CI/CD service, such as GitHub Actions.&lt;/p&gt;

&lt;p&gt;If you're here, then you already know that secrets are hidden from CI/CD logs with &lt;code&gt;***&lt;/code&gt;, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;openssl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Recover With OpenSSL&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-20.04&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;MY_CLIENT_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.MY_CLIENT_SECRET }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "MY_CLIENT_SECRET (***)     = ${MY_CLIENT_SECRET}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Output&lt;/span&gt;
MY_CLIENT_SECRET &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;***&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;***&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above isn't very helpful since this is the situation you're probably in right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick And Dirty (Dangerous)
&lt;/h2&gt;

&lt;p&gt;For private repositories, it's possible to use &lt;a href="https://en.wikipedia.org/wiki/Base64"&gt;base64&lt;/a&gt; to &lt;strong&gt;encode&lt;/strong&gt; a secret before printing it to the CI/CD service logs; this way, GitHub Actions won't hide the secret with &lt;code&gt;***&lt;/code&gt;. Then, copy the &lt;strong&gt;encoded&lt;/strong&gt; value and &lt;strong&gt;decode&lt;/strong&gt; it locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Recovering secrets&lt;/span&gt;

&lt;span class="c1"&gt;# Assumption:&lt;/span&gt;
&lt;span class="c1"&gt;# You've created the following GitHub secrets in your repository:&lt;/span&gt;
&lt;span class="c1"&gt;# MY_CLIENT_ID - encode/decode with base64 - useful for private repositories&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;base64&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Recover With Base64&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-20.04&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;MY_CLIENT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.MY_CLIENT_ID }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "MY_CLIENT_ID (***)    = ${MY_CLIENT_ID}"&lt;/span&gt;
          &lt;span class="s"&gt;echo "MY_CLIENT_ID (base64) = $(echo ${MY_CLIENT_ID} | base64)"&lt;/span&gt;
          &lt;span class="s"&gt;echo "Copy the above value, and then execute locally:"&lt;/span&gt;
          &lt;span class="s"&gt;echo "echo PASTE_HERE | base64 -D"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe2hjlmn74z6qg9j085g6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe2hjlmn74z6qg9j085g6.png" alt="recover-github-secret-base64" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above method is hazardous as anyone can decode the secret, so for public repositories, this is a no-go. And here's proof why it is &lt;strong&gt;super dangerous&lt;/strong&gt;, assuming the printed &lt;strong&gt;encoded&lt;/strong&gt; value is &lt;code&gt;c29tZS1jbGllbnQtaWQtdmFsdWUK&lt;/code&gt; ...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;c29tZS1jbGllbnQtaWQtdmFsdWUK | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt;
&lt;span class="c"&gt;# some-client-id-value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've just exposed &lt;code&gt;MY_CLIENT_ID&lt;/code&gt; to the whole world ... I'm terrified.&lt;/p&gt;

&lt;h2&gt;
  
  
  How To Recover A Secret From A CICD Service
&lt;/h2&gt;

&lt;p&gt;The best way to recover a secret from a CICD system without exposing it to the outside world is to &lt;strong&gt;encrypt&lt;/strong&gt; the secret before printing it to the CI/CD logs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Recovering secrets&lt;/span&gt;

&lt;span class="c1"&gt;# Assumption:&lt;/span&gt;
&lt;span class="c1"&gt;# You've created the following GitHub secrets in your repository:&lt;/span&gt;
&lt;span class="c1"&gt;# MY_CLIENT_SECRET - encrypt/decrypt with openssl - useful for public and private repositories&lt;/span&gt;
&lt;span class="c1"&gt;# MY_OPENSSL_PASSWORD - used to protect secrets&lt;/span&gt;
&lt;span class="c1"&gt;# MY_OPENSSL_ITER - Use a number of iterations on the password to derive the encryption key.&lt;/span&gt;
&lt;span class="c1"&gt;#                   High values increase the time required to brute-force the resulting file.&lt;/span&gt;
&lt;span class="c1"&gt;#                   This option enables the use of PBKDF2 algorithm to derive the key.&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;openssl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Recover With OpenSSL&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-20.04&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;MY_CLIENT_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.MY_CLIENT_SECRET }}&lt;/span&gt;
          &lt;span class="na"&gt;MY_OPENSSL_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.MY_OPENSSL_PASSWORD }}&lt;/span&gt;
          &lt;span class="na"&gt;MY_OPENSSL_ITER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.MY_OPENSSL_ITER }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "MY_CLIENT_SECRET (***)     = ${MY_CLIENT_SECRET}"&lt;/span&gt;
          &lt;span class="s"&gt;echo "MY_CLIENT_SECRET (openssl) = $(echo "${MY_CLIENT_SECRET}" | openssl enc -e -aes-256-cbc -a -pbkdf2 -iter ${MY_OPENSSL_ITER} -k "${MY_OPENSSL_PASSWORD}")"&lt;/span&gt;
          &lt;span class="s"&gt;echo "Copy the above value, and then execute locally:"&lt;/span&gt;
          &lt;span class="s"&gt;echo "echo PASTE_HERE | openssl base64 -d | openssl enc -d -pbkdf2 -iter \$MY_OPENSSL_ITER -aes-256-cbc -k \$MY_OPENSSL_PASSWORD"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxvdjfmb0ensf5tji5hkr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxvdjfmb0ensf5tji5hkr.png" alt="recover-github-secret-openssl" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The only way to &lt;strong&gt;decrypt&lt;/strong&gt; the above string &lt;code&gt;U2FsdGVkX1+6/+7bvNG/Ga7siAI994FkMUn5Njzn4zyNwvf8qM3MY0MMmd9sCFvz&lt;/code&gt; is to use the right number of &lt;code&gt;iter&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt;, otherwise you'll have to use a &lt;a href="https://en.wikipedia.org/wiki/Brute-force_attack"&gt;brute-force attack&lt;/a&gt;, good luck with that :)&lt;/p&gt;

&lt;p&gt;Here's how I &lt;strong&gt;decrypted&lt;/strong&gt; the above value on my local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;U2FsdGVkX1+CeN0/ScQLZGU8f0ix86fh1oLJg/1M+o2lbCM+pBA8BIUCbkHMCjRZ &lt;span class="se"&gt;\&lt;/span&gt;
| openssl &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
| openssl enc &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-pbkdf2&lt;/span&gt; &lt;span class="nt"&gt;-iter&lt;/span&gt; &lt;span class="nv"&gt;$MY_OPENSSL_ITER&lt;/span&gt; &lt;span class="nt"&gt;-aes-256-cbc&lt;/span&gt; &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="nv"&gt;$MY_OPENSSL_PASSWORD&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;I suggest creating a separate workflow for recovering CI/CD (GitHub) secrets, like &lt;code&gt;.github/workflows/recover-github-secrets.yml&lt;/code&gt;, followed by running the workflow and then deleting its logs once you're done recovering the secret.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuh0d5qb8bqagjwx6acws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuh0d5qb8bqagjwx6acws.png" alt="recover-github-secret-delete-logs" width="800" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/unfor19/gha-play/runs/7149023860?check_suite_focus=true"&gt;An example of how the above YAML files are executed in GitHub Actions&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/unfor19/gha-play/blob/master/.github/workflows/recover-github-secrets.yml"&gt;GitHub Actions full YAML example of using &lt;code&gt;base64&lt;/code&gt; and &lt;code&gt;openssl&lt;/code&gt; for recovering GitHub secrets&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>cicd</category>
      <category>githubactions</category>
      <category>secrets</category>
    </item>
    <item>
      <title>How To Test A GitHub Action</title>
      <dc:creator>Meir Gabay</dc:creator>
      <pubDate>Sat, 04 Dec 2021 20:01:48 +0000</pubDate>
      <link>https://forem.com/unfor19/how-to-test-a-github-action-3epl</link>
      <guid>https://forem.com/unfor19/how-to-test-a-github-action-3epl</guid>
      <description>&lt;h3&gt;
  
  
  My Workflow
&lt;/h3&gt;

&lt;p&gt;The project &lt;a href="https://github.com/unfor19/hero-action"&gt;unfor19/hero-action&lt;/a&gt; is an All-In-One action to a test GitHub Action.&lt;/p&gt;

&lt;h3&gt;
  
  
  Submission Categories
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Maintainer Must-Haves&lt;/li&gt;
&lt;li&gt;Wacky Wildcards - I'm adding this one because it's a "hacky" way of using GitHub Actions to test an action (feels like a recursion)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Yaml File or Link to Code
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/unfor19"&gt;
        unfor19
      &lt;/a&gt; / &lt;a href="https://github.com/unfor19/hero-action"&gt;
        hero-action
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      All-in-one action to develop and maintain GitHub Actions.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;hero-action&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/unfor19/hero-action/actions?query=workflow%3Atesting"&gt;&lt;img src="https://github.com/unfor19/hero-action/workflows/testing/badge.svg" alt="testing"&gt;&lt;/a&gt;
&lt;a href="https://github.com/unfor19/hero-action-test/actions?query=workflow%3Atest-action"&gt;&lt;img src="https://github.com/unfor19/hero-action-test/workflows/test-action/badge.svg" alt="test-action"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;An All-In-One action to test a GitHub Action.&lt;/p&gt;
&lt;p&gt;Tested in &lt;a href="https://github.com/unfor19/hero-action-test/actions?query=workflow%3Atest-action"&gt;unfor19/hero-action-test&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Generate a new &lt;a href="https://github.com/settings/tokens"&gt;Personal Access Token&lt;/a&gt; with the scope: &lt;strong&gt;repo + workflow&lt;/strong&gt;. Keep this token in a safe place we'll use it later on.&lt;/li&gt;
&lt;li&gt;Add the following workflow &lt;code&gt;.github/workflows/testing.yml&lt;/code&gt; to your &lt;strong&gt;action&lt;/strong&gt;'s repository, e.g. &lt;code&gt;hero-action&lt;/code&gt;
&lt;div class="highlight highlight-source-yaml notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-ent"&gt;name&lt;/span&gt;: &lt;span class="pl-s"&gt;testing&lt;/span&gt;
&lt;span class="pl-ent"&gt;on&lt;/span&gt;:
  &lt;span class="pl-ent"&gt;push&lt;/span&gt;:
    &lt;span class="pl-ent"&gt;branches&lt;/span&gt;: &lt;span class="pl-s"&gt;[master]&lt;/span&gt;
    &lt;span class="pl-ent"&gt;paths-ignore&lt;/span&gt;:
      - &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;README.md&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
  &lt;span class="pl-ent"&gt;workflow_dispatch&lt;/span&gt;:

&lt;span class="pl-ent"&gt;jobs&lt;/span&gt;:
  &lt;span class="pl-ent"&gt;dispatch_test_action&lt;/span&gt;:
    &lt;span class="pl-ent"&gt;name&lt;/span&gt;: &lt;span class="pl-s"&gt;Dispatch Test Action&lt;/span&gt;
    &lt;span class="pl-ent"&gt;runs-on&lt;/span&gt;: &lt;span class="pl-s"&gt;ubuntu-20.04&lt;/span&gt;
    &lt;span class="pl-ent"&gt;steps&lt;/span&gt;:
      - &lt;span class="pl-ent"&gt;uses&lt;/span&gt;: &lt;span class="pl-s"&gt;actions/checkout@v2&lt;/span&gt;
      - &lt;span class="pl-ent"&gt;name&lt;/span&gt;: &lt;span class="pl-s"&gt;Workflow Dispatch Status&lt;/span&gt;
        &lt;span class="pl-ent"&gt;uses&lt;/span&gt;: &lt;span class="pl-s"&gt;unfor19/hero-action@v1.0.2&lt;/span&gt;
        &lt;span class="pl-ent"&gt;with&lt;/span&gt;:
          &lt;span class="pl-ent"&gt;action&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;dispatch-status&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
          &lt;span class="pl-ent"&gt;src_repository&lt;/span&gt;: &lt;span class="pl-s"&gt;${{ github.repository }}&lt;/span&gt;
          &lt;span class="pl-ent"&gt;src_workflow_name&lt;/span&gt;: &lt;span class="pl-s"&gt;${{ github.workflow }}&lt;/span&gt;
          &lt;span class="pl-ent"&gt;src_sha&lt;/span&gt;: &lt;span class="pl-s"&gt;${{ github.sha }}&lt;/span&gt;
          &lt;span class="pl-ent"&gt;target_repository&lt;/span&gt;: &lt;span class="pl-s"&gt;${{ github.repository }}-test&lt;/span&gt;
          &lt;span class="pl-ent"&gt;target_workflow_name&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;test-action.yml&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
          &lt;span class="pl-ent"&gt;gh_token&lt;/span&gt;: &lt;span class="pl-s"&gt;${{ secrets.GH_TOKEN }} &lt;/span&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; scope: repo + workflow&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;Create a new GitHub repository to test your action, e.g. &lt;code&gt;hero-action-test&lt;/code&gt;, add…&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/unfor19/hero-action"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;The action repository, &lt;code&gt;my-repo&lt;/code&gt;, triggers a workflow in the test repository, &lt;code&gt;my-repo-test&lt;/code&gt;. Meanwhile, the &lt;code&gt;my-repo&lt;/code&gt; is "stuck" with the commit status &lt;strong&gt;pending&lt;/strong&gt;.
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2DX8oyII--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.meirg.co.il/hero-action/hero-action-commit-status-pending.png" alt="hero-action-commit-status-pending" width="800" height="367"&gt;
&lt;/li&gt;
&lt;li&gt;The repository &lt;code&gt;my-repo-test&lt;/code&gt; tests the action by using it, see &lt;a href="https://github.com/unfor19/hero-action-test/blob/master/.github/workflows/test-action.yml#L37-L48"&gt;unfor19/hero-action-test&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Upon success/failure, the test action &lt;code&gt;my-repo-test&lt;/code&gt; &lt;a href="https://docs.github.com/en/rest/reference/repos#create-a-commit-status"&gt;creates a commit status&lt;/a&gt; in the action's repository, this updates the status of the &lt;strong&gt;pending&lt;/strong&gt; workflow with &lt;strong&gt;success&lt;/strong&gt; or &lt;strong&gt;failure&lt;/strong&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FCXTqwXG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.meirg.co.il/hero-action/hero-action-commit-status-success.png" alt="hero-action-commit-status-success" width="800" height="364"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Additional Resources / Info
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Using this action in:

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/unfor19/replacer-action"&gt;unfor19/replacer-action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/unfor19/install-aws-cli-action"&gt;unfor19/install-aws-cli-action&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>actionshackathon21</category>
      <category>devops</category>
      <category>cicd</category>
      <category>github</category>
    </item>
    <item>
      <title>How To Develop A Progressive Web Application On An Android Device</title>
      <dc:creator>Meir Gabay</dc:creator>
      <pubDate>Thu, 02 Dec 2021 20:46:17 +0000</pubDate>
      <link>https://forem.com/unfor19/how-to-develop-a-progressive-web-application-on-an-android-device-39jj</link>
      <guid>https://forem.com/unfor19/how-to-develop-a-progressive-web-application-on-an-android-device-39jj</guid>
      <description>&lt;p&gt;In the past few weeks, I've been wondering how the whole eco-system of a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps" rel="noopener noreferrer"&gt;Progressive Web Application&lt;/a&gt; (PWA) works. Of course, I need to get my hands dirty and code something to understand it.&lt;/p&gt;

&lt;p&gt;My main goal is to provision a local development environment, which hot-reloads (code changed) the application on a physical Android device.&lt;/p&gt;

&lt;p&gt;The main challenge was figuring out a way to access the PWA, which runs on my local machine from my Android device (Samsung Galaxy S10). Why? Because &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Installable_PWAs#requirements" rel="noopener noreferrer"&gt;PWA requires HTTPS access&lt;/a&gt; so using IP addresses is not an option.&lt;/p&gt;

&lt;p&gt;Ladies and gentlemen, I present to you - &lt;a href="https://github.com/unfor19/pwa-quasar-local" rel="noopener noreferrer"&gt;unfor19/pwa-quasar-local&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This project demonstrates how to develop a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps" rel="noopener noreferrer"&gt;Progressive Web Application&lt;/a&gt; (PWA) locally on an Android device, using the &lt;a href="https://quasar.dev/" rel="noopener noreferrer"&gt;Quasar Framework v2&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Final Results
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;IMPORTANT&lt;/strong&gt;: Images may look weird on DEV.to; unsure why. Go to the GitHub repo &lt;a href="https://github.com/unfor19/pwa-quasar-local" rel="noopener noreferrer"&gt;unfor19/pwa-quasar-local&lt;/a&gt; if you experience funny widths and heights&lt;/p&gt;

&lt;p&gt;I took screenshots with my Android device to document the whole user experience of installing a PWA for the first time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessed PWA From Android Device
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Add to Home Screen&lt;/code&gt; popup appears!&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Clicked Add To Home Screen
&lt;/h3&gt;

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

&lt;h3&gt;
  
  
  Clicked Install
&lt;/h3&gt;

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

&lt;h3&gt;
  
  
  Installation Completed
&lt;/h3&gt;

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

&lt;h3&gt;
  
  
  PWA Appears On The Device's Apps List
&lt;/h3&gt;

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

&lt;h3&gt;
  
  
  PWA Has A Cool Splashscreen
&lt;/h3&gt;

&lt;p&gt;That is thanks to &lt;a href="https://quasar.dev/" rel="noopener noreferrer"&gt;Quasar&lt;/a&gt; which does it, as always, automatically.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  First Run After Installation
&lt;/h3&gt;

&lt;p&gt;The application runs on the device as if it were a "normal application".&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;It was a true joy to work with Quasar since it made the whole process of generating a PWA out-of-the-box without writing a single line of code. So head over to &lt;a href="https://github.com/unfor19/pwa-quasar-local" rel="noopener noreferrer"&gt;unfor19/pwa-quasar-local&lt;/a&gt; and do your PWA magic!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>vue</category>
      <category>programming</category>
    </item>
    <item>
      <title>Improving email deliverability with DNS records</title>
      <dc:creator>Meir Gabay</dc:creator>
      <pubDate>Fri, 26 Nov 2021 23:30:50 +0000</pubDate>
      <link>https://forem.com/unfor19/improving-email-deliverability-with-dns-records-38ep</link>
      <guid>https://forem.com/unfor19/improving-email-deliverability-with-dns-records-38ep</guid>
      <description>&lt;p&gt;As always, every day brings up new challenges, and today I faced one of my greatest fears - dealing with mailing DNS records.&lt;/p&gt;

&lt;p&gt;Up until today, I was only familiar with &lt;em&gt;A Record&lt;/em&gt;, &lt;em&gt;CNAME Record&lt;/em&gt;, and &lt;em&gt;TXT Record&lt;/em&gt;. A while ago, I used the &lt;em&gt;MX Record&lt;/em&gt; while setting up my mailbox for my domain, but I don't quite remember its purpose, but enough about me.&lt;/p&gt;

&lt;p&gt;Mail eXchangers, such as Gmail, Yahoo, Outlook, check the validity of incoming email messages. As part of the validation process, the Mail eXchanger queries the DNS records of the sender and evaluates the sender's reputation.&lt;/p&gt;

&lt;p&gt;The sender's reputation affects how the Mail eXchanger classifies the message and whether or not it should be marked as spam before it is delivered to the receiver. In some cases, the message is blocked entirely by the Mail eXchanger, and the receiver is unaware of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Reputation
&lt;/h2&gt;

&lt;p&gt;There are many algorithms out there for evaluating an email sender's reputation. To put it on a scale, I classified the reputation level into four levels - &lt;em&gt;Very Low&lt;/em&gt;, &lt;em&gt;Low&lt;/em&gt;, &lt;em&gt;Standard&lt;/em&gt;, and &lt;em&gt;Best&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Very Low
&lt;/h3&gt;

&lt;p&gt;Blocked completely without even bothering the receiver, here are two common error messages that may appear in the ESP's logs if the sender's email has sent too many emails in a short period.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;421 4.7.0 [TS01] Messages from &amp;lt;1.2.3.4&amp;gt; temporarily deferred due to user complaints
&amp;lt;1.2.3.4&amp;gt; ;see http://postmaster.yahoo.com/421-ts01.html
&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;553 5.7.1 [BL21] Connections will not be accepted from 1.2.3.4,
    because the ip is in Spamhaus's list; 
see http://postmaster.yahoo.com/550-bl23.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://glockapps.com/blog/remove-ip-address-yahoo-blacklist/"&gt;Above snippets from - glockapps.com - How To Remove Your IP From Yahoo Blacklist – Yahoo Blacklist Checker&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If there's a need to send large volumes of email messages, it is recommended to purchase a &lt;a href="https://docs.sendgrid.com/ui/sending-email/warming-up-an-ip-address"&gt;dedicated IP with a warm-up mechanism&lt;/a&gt;. The subject is broadly explained in the provided link, though in short, it's another method of identifying yourself as a trusted sender, by saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I own a unique IP address because I'm confident that Mail eXchangers won't block me. I know that it's easy to block an IP address, so yeah, I'm that confident."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Low Reputation
&lt;/h3&gt;

&lt;p&gt;Emails are marked as spam due to the low authenticity of the sender or previous reports of other users. But, most of the time, it's because the content of the message is not specific enough to the receiver.&lt;/p&gt;

&lt;p&gt;For example, Willy is a Gmail customer, and he is interested in computer science and surfing. Then, out of the blue, Willy receives an email message about a great body lotion for women. Though Willy may have subscribed to some cosmetics stores websites, if the message isn't specific enough, like "Hi Willy," then it &lt;strong&gt;might&lt;/strong&gt; be marked as spam.&lt;/p&gt;

&lt;p&gt;Send the relevant content to the appropriate audience to avoid getting here.&lt;/p&gt;
&lt;h3&gt;
  
  
  Standard Reputation
&lt;/h3&gt;

&lt;p&gt;Email messages are getting to the client's mailbox as they should, though, in some Mail eXchangers such as Gmail, messages could move to the &lt;a href="https://developers.google.com/gmail/promotab/faq#why_do_i_see_my_email_in_a_bundle_and_i_have_not_added_the_annotation"&gt;Promotions tab&lt;/a&gt;, while it wasn't intended.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"... Gmail will recommend emails based on previous user engagement, regardless of whether annotations are present."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Best Reputation
&lt;/h3&gt;

&lt;p&gt;Email messages are getting to the client's mailbox as the &lt;strong&gt;sender intended&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Some businesses may want to get on Gmail's &lt;a href="https://developers.google.com/gmail/promotab/overview"&gt;Promotions tab on purpose&lt;/a&gt;; it all depends on the sender's needs.&lt;/p&gt;




&lt;p&gt;Are you ready to explore the dark depths of mailing DNS records? Let's go!&lt;/p&gt;



&lt;h2&gt;
  
  
  Baseline
&lt;/h2&gt;

&lt;p&gt;Previously, I mentioned that I'm familiar with a few DNS records, but that doesn't mean that we're on the same page. So line up!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Record Type&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Example Key Pair Values&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;IP addresses&lt;/td&gt;
&lt;td&gt;Name: virtual-machine.meirg.co.il (For example, &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-instance-addressing.html#ip-addressing-eips" rel="noopener noreferrer"&gt;AWS Elastic IP&lt;/a&gt;)&lt;br&gt;Value: 123.123.123.123&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CNAME&lt;/td&gt;
&lt;td&gt;Canonical name for other domain name&lt;/td&gt;
&lt;td&gt;Name: meirg.co.il&lt;br&gt;Value: meirg-website.pages.dev (&lt;a href="https://pages.cloudflare.com/" rel="noopener noreferrer"&gt;Cloudflare Pages)&lt;/a&gt;&lt;br&gt;&lt;br&gt;Name: www.meirg.co.il (&lt;a href="https://support.cloudflare.com/hc/en-us/articles/200172286-Configuring-URL-forwarding-or-redirects-with-Cloudflare-Page-Rules" rel="noopener noreferrer"&gt;Redirects to meirg.co.il&lt;/a&gt;)&lt;br&gt;Value: meirg.co.il&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;Domain validation and authenticity&lt;/td&gt;
&lt;td&gt;Name: meirg.co.il (&lt;a href="https://support.google.com/a/answer/183895?hl=en" rel="noopener noreferrer"&gt;Google Domain Verification&lt;/a&gt;)&lt;br&gt;Value: "google-site-verification=2NOO....XgotefAU"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MX&lt;/td&gt;
&lt;td colspan="2"&gt;Let's leave it as a mystery for now&lt;br&gt;(keep your Google Search gun down!)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Plot
&lt;/h2&gt;

&lt;p&gt;I need to improve the email deliverability of outgoing emails. The request first came from the marketing expert of one of my customers, and he told me &lt;em&gt;"We need you for the DNS part."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ok, I'm up for it; let's set up the DNS records and assist my customer in reaching out to his end-users.&lt;/p&gt;

&lt;p&gt;But how can I make sure that I succeed? I mean, doing/learning stuff is fun, it really is, but how will I know if my changes impacted my &lt;a href="https://www.sparkpost.com/resources/email-explained/email-sender-reputation/#:~:text=An%20email%20sender%20reputation%20is,an%20organization%20that%20sends%20email.&amp;amp;text=The%20higher%20the%20score%2C%20the,of%20recipients%20on%20their%20network."&gt;email sender reputation&lt;/a&gt;? How do I know if there's a better open/click rate for my emails?&lt;/p&gt;

&lt;p&gt;The above questions are widespread when the need to send emails arises. That usually comes with many requirements, such as "unsubscribe mechanism", "group contacts by segments", "statistics of open/click", and the list goes on, depending on your marketing manager 😉&lt;/p&gt;

&lt;p&gt;Developing a system that can measure open/click rate and handle email/contacts management will take ages to build...&lt;/p&gt;

&lt;p&gt;So maybe I should ...&lt;/p&gt;
&lt;h2&gt;
  
  
  Use professional help
&lt;/h2&gt;

&lt;p&gt;Here comes &lt;a href="https://www.activecampaign.com/glossary/email-service-provider"&gt;ESP&lt;/a&gt; into play! ESP stands for Email Service Provider, and that means there are cloud providers out there who are willing to send emails on my behalf and enable me to manage the whole email delivery system in a one-stop-shop.&lt;/p&gt;

&lt;p&gt;There are many ESPs out there such as &lt;a href="https://workspace.google.com/products/gmail/"&gt;Google Workspace&lt;/a&gt;, &lt;a href="https://sendgrid.com/"&gt;SendGrid&lt;/a&gt; and &lt;a href="https://mailchimp.com"&gt;Mailchimp&lt;/a&gt;. Each provider has its pros and cons, but the common ground is - &lt;strong&gt;they all send emails on behalf of your domain&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The difference usually comes when an ESP, like SendGrid, offers comprehensive services like &lt;a href="https://docs.sendgrid.com/ui/account-and-settings/how-to-set-up-domain-authentication#using-automated-security"&gt;Automated Security&lt;/a&gt; which enabled you to purchase &lt;a href="https://docs.sendgrid.com/ui/account-and-settings/dedicated-ip-addresses"&gt;a dedicated IP&lt;/a&gt; as a service (we'll get to that).&lt;/p&gt;

&lt;p&gt;We covered ESPs, moving on to getting familiar with industry standards for improving email deliverability.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: I might mention SendGrid a lot during this blog post, though I'm not biased towards any ESP; SendGrid is simply the service I've been using recently, so it's easier for me to make references. Most ESPs offer a free/trial plan so you can explore their features and then decide, and I recommend evaluating at least two ESPs before picking one.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Standards For Better Email Delivery
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://support.google.com/a/answer/10583557?hl=en&amp;amp;ref_topic=9061731"&gt;According to Google&lt;/a&gt;, at least four standards should be implemented (in this order) &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SPF&lt;/li&gt;
&lt;li&gt;DKIM&lt;/li&gt;
&lt;li&gt;DMARC&lt;/li&gt;
&lt;li&gt;(Optional) BIMI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And, I'm adding &lt;a href="https://docs.sendgrid.com/ui/account-and-settings/dedicated-ip-addresses#what-are-dedicated-ip-addresses"&gt;A dedicated IP&lt;/a&gt; to the mix.&lt;/p&gt;

&lt;p&gt;Mail eXchangers check if the sender meets industry standards by querying its DNS record, and according to that, evaluate the sender's reputation. The sender evaluation process includes other methods, such as looking in &lt;a href="https://multirbl.valli.org/lookup/meirg.co.il.html"&gt;https://multirbl.valli.org/&lt;/a&gt; to see if the source domain (eventually IP) of the sender is marked as an "official spammer", that all it depends on the Mail eXchanger. The more standards the sender meets, the higher the chances the sender's email message will arrive at its target audience (receiver).&lt;/p&gt;

&lt;p&gt;The illustration below is for the &lt;em&gt;SPF&lt;/em&gt; record. Still, the same communication process is the same for all standards, where the Receiving Email Server (Mail eXchanger) validates the email sender's authenticity and reputation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9zCC5nv_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://twilio-cms-prod.s3.amazonaws.com/original_images/spf_mail_flow.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9zCC5nv_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://twilio-cms-prod.s3.amazonaws.com/original_images/spf_mail_flow.jpeg" alt="spf_mail_flow" width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Image Source: &lt;a href="https://twilio-cms-prod.s3.amazonaws.com/original_images/spf_mail_flow.jpeg"&gt;https://twilio-cms-prod.s3.amazonaws.com/original_images/spf_mail_flow.jpeg&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  SPF
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Definition: &lt;a href="https://datatracker.ietf.org/doc/html/rfc7208"&gt;Sender Policy Framework&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Remember with: &lt;code&gt;S&lt;/code&gt; for "&lt;strong&gt;S&lt;/strong&gt;ending emails"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This record allows ESPs such as SendGrid, Gmail, Mailchimp to send emails on behalf of your domain. So if my domain name is &lt;em&gt;meirg.co.il&lt;/em&gt; and I would like to send emails with Google Workspace, I need to add &lt;a href="https://support.google.com/a/answer/10684623?hl=en"&gt;Google's SPF&lt;/a&gt; record to my domain &lt;em&gt;meirg.co.il&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;And the funny thing about an &lt;em&gt;SPF Record&lt;/em&gt;, that it's actually a &lt;em&gt;TXT Record&lt;/em&gt;. Let me remind you that &lt;em&gt;TXT Records&lt;/em&gt; are for "Domain validation and authenticity," so it's nothing more than adding a &lt;em&gt;TXT Record&lt;/em&gt; and adding a value that is specific for the &lt;em&gt;SPF&lt;/em&gt; standard.&lt;/p&gt;

&lt;p&gt;Here's an example for setting up two &lt;em&gt;SPF&lt;/em&gt; records, one for &lt;a href="https://support.google.com/a/answer/10685031"&gt;Google Workspace&lt;/a&gt; and the other for &lt;a href="https://docs.sendgrid.com/ui/account-and-settings/how-to-set-up-domain-authentication#before-you-begin:~:text=HOST/NAME"&gt;SendGrid's SPF&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;# Google Workspace
Type: TXT
Name: meirg.co.il
Value: v=spf1 include:_spf.google.com ~all

# Sendgrid
Type: TXT
Name: em123.meirg.co.il
Value: u12345678.wl123.sendgrid.net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the weird thing? &lt;code&gt;Google Workspace&lt;/code&gt; has an &lt;a href="https://dmarcian.com/spf-syntax-table/"&gt;SPF expression&lt;/a&gt;, while &lt;code&gt;SendGrid&lt;/code&gt; provides a &lt;em&gt;CNAME&lt;/em&gt; as a value in the &lt;em&gt;TXT&lt;/em&gt; record. My gut told me that the &lt;em&gt;CNAME&lt;/em&gt; should eventually resolve to an &lt;em&gt;SPF expression&lt;/em&gt;, so I checked it with &lt;a href="https://tools.wordtothewise.com/authentication"&gt;Authentication @ tools.wordtothewise.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I tested it by navigating to &lt;code&gt;https://tools.wordtothewise.com/dns/txt/em123.meirg.co.il&lt;/code&gt;, which resolved in ... Drums ... &lt;code&gt;u12345678.wl123.sendgrid.net&lt;/code&gt; as a &lt;em&gt;TXT&lt;/em&gt; record which contains the following expression.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# SendGrid's CNAME resolves to SPF expression
v=spf1 ip4:122.122.122.122 -all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is quite a wonder that eventually, it's &lt;code&gt;ip4:122.122.122.122&lt;/code&gt; and not a CNAME, as we saw in Google Workspace &lt;code&gt;include:_spf.google.com&lt;/code&gt;. This wonder is called a &lt;a href="https://postmarkapp.com/blog/how-to-check-your-ip-reputation#why-is-ip-reputation-important"&gt;dedicated IP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Declaring an &lt;em&gt;SPF Record&lt;/em&gt;, or any other record that is related to mailing, usually comes with a complete guide on how to create it, check &lt;a href="https://support.google.com/a/answer/10685031"&gt;Google Workspace Basic Setup for SPF&lt;/a&gt; which also explains &lt;a href="https://support.google.com/a/answer/10685031#:~:text=servers.mail.net"&gt;how to include multiple ESPs in a single SPF Record&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  When should I use a single SPF expression?
&lt;/h3&gt;

&lt;p&gt;Getting back to my previous example of Google Workspace and SendGrid; Luckily, SendGrid provides a particular &lt;em&gt;TXT Record&lt;/em&gt;, which includes a unique subdomain, &lt;em&gt;em123&lt;/em&gt;; this makes the setup is really nice since you don't modify existing with the fear of breaking something.&lt;/p&gt;

&lt;p&gt;Assuming your ESP*&lt;em&gt;s&lt;/em&gt;* required the &lt;em&gt;TXT Record&lt;/em&gt; of &lt;em&gt;SPF&lt;/em&gt; to be present in the root domain, e.g., &lt;em&gt;meirg.co.il&lt;/em&gt;, you'll have to edit your existing &lt;em&gt;SPF expression&lt;/em&gt; and add additional ESPs. For example, here's how you would write an &lt;em&gt;SPF expression&lt;/em&gt; when using both Google Workspace and &lt;a href="https://www.mailgun.com/"&gt;Mailgun&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;# Google Workspace + Mailgun
Type: TXT
Name: meirg.co.il
Value: v=spf1 include:_spf.google.com include:mailgun.org ~all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: Regarding which ESP you should choose (Google/SendGrid/Mailchimp/Mailgun, etc.), it depends on your needs. I use Google Workspace for receiving/sending emails on behalf of &lt;em&gt;human beings&lt;/em&gt; who use &lt;code&gt;@meirg.co.il&lt;/code&gt; and SendGrid for sending automated emails with my web application &lt;code&gt;@meirg.co.il&lt;/code&gt;. By the way, &lt;a href="https://mailchimp."&gt;Mailchimp&lt;/a&gt; is also excellent, I used it a long time ago, and I truly enjoyed it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Recap on SPF
&lt;/h3&gt;

&lt;p&gt;A &lt;em&gt;TXT&lt;/em&gt; record that contains an &lt;a href="https://dmarcian.com/spf-syntax-table/"&gt;SPF expression&lt;/a&gt; or a &lt;em&gt;CNAME&lt;/em&gt; that is eventually resolved to an &lt;em&gt;SPF expression&lt;/em&gt;. The &lt;em&gt;SPF expression&lt;/em&gt; contains an authorization policy about which service can send emails on your domain's behalf.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;IMPORTANT&lt;/strong&gt; Make sure you send emails from the same address that you've authenticated in the ESP (SendGrid, Google), for example, &lt;strong&gt;DO NOT TRY THE FOLLOWING&lt;/strong&gt;, sending emails from &lt;em&gt;&lt;a href="mailto:no-reply@meirg.com"&gt;no-reply@meirg.com&lt;/a&gt;&lt;/em&gt;. In contrast, my authenticated domain address is &lt;em&gt;meirg.co.il&lt;/em&gt;. That can easily happen if you're using &lt;a href="https://docs.sendgrid.com/ui/account-and-settings/api-keys"&gt;SendGrid - API keys to send emails&lt;/a&gt; since the &lt;em&gt;FROM&lt;/em&gt; field is not protected, the API call can "endure" anything you shove it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  DKIM
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Definition: &lt;a href="https://datatracker.ietf.org/doc/html/rfc6376"&gt;DomainKeys Identified Mail (Signatures)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Remember with: &lt;code&gt;K&lt;/code&gt; for &lt;strong&gt;K&lt;/strong&gt;eys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This one is going to be very short compared to &lt;em&gt;SPF&lt;/em&gt; since we've already covered all basics,&lt;/p&gt;

&lt;p&gt;I like to think about &lt;em&gt;DKIM&lt;/em&gt; as the HTTPS of emails. So &lt;em&gt;DKIM&lt;/em&gt; is a way for an email sender to &lt;a href="https://support.google.com/a/answer/174124?hl=en"&gt;increase authenticity by adding a signature to the headers of an email message&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"...DKIM adds an encrypted signature to the header of all outgoing messages. Email servers (Mail eXchangers) that get signed messages use DKIM to decrypt the message header and verify the message was not changed after it was sent."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's how the &lt;em&gt;DKIM&lt;/em&gt; record looks for my domain &lt;em&gt;meirg.co.il&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Type: TXT
Name: google._domainkey.meirg.co.il
Value: v=DKIM1; k=rsa; p=Ultra-Long-Key-PUBLIC-KEY-392-chars
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The subdomain &lt;code&gt;google._domainkey&lt;/code&gt; was &lt;a href="https://support.google.com/a/answer/174126?hl=en&amp;amp;ref_topic=2752442"&gt;generated by Google&lt;/a&gt; for my domain &lt;em&gt;meirg.co.il&lt;/em&gt;. After generating the &lt;em&gt;DKIM&lt;/em&gt; record, I followed the instructions and authenticated my domain by adding a &lt;em&gt;TXT&lt;/em&gt; record with the value provided by Google.&lt;/p&gt;

&lt;p&gt;If I intend to use other ESPs, I can generate a subdomain per ESP, so &lt;a href="https://docs.sendgrid.com/ui/account-and-settings/dkim-records#example-dkim-record-automated-security-off"&gt;for SendGrid, it'll be s1._domainkey&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As you can see, the &lt;code&gt;*._domainkey&lt;/code&gt; is the critical value, as that's the one that the Mail eXchanger is checking. So if a DNS record, such as &lt;em&gt;TXT&lt;/em&gt; or &lt;em&gt;CNAME&lt;/em&gt;, is named with &lt;code&gt;_domainkey&lt;/code&gt;, you can classify it safely as &lt;em&gt;DKIM&lt;/em&gt;. The &lt;code&gt;*._domainkey&lt;/code&gt; prefix, such as &lt;code&gt;google&lt;/code&gt; and &lt;code&gt;s1&lt;/code&gt;, is called a selector. One domain can have multiple &lt;em&gt;DKIM&lt;/em&gt; selectors when using various ESPs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: Curious about my public key? Dig for it! &lt;code&gt;dig TXT google._domainkey.meirg.co.il&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  DMARC - Here comes D-MARC tu du du du
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Definition: &lt;a href="https://datatracker.ietf.org/doc/html/rfc7489"&gt;Domain-based Message Authentication, Reporting &amp;amp; Conformance&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Remember with: &lt;code&gt;DM&lt;/code&gt; "&lt;strong&gt;D&lt;/strong&gt;omain &lt;strong&gt;M&lt;/strong&gt;onitoring" and &lt;code&gt;D-MARC&lt;/code&gt; like "the man(ager)"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We covered &lt;em&gt;SPF&lt;/em&gt; and &lt;em&gt;DKIM&lt;/em&gt;, but how do we know that our email messages are authenticated with &lt;em&gt;SPF&lt;/em&gt; and signed with &lt;em&gt;DKIM&lt;/em&gt;? Is there a way to track down it down with numbers? There is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dmarc.org/"&gt;DMARC&lt;/a&gt; is an email authentication standard - Meeting up this standard means your &lt;em&gt;SPF&lt;/em&gt; and &lt;em&gt;DKIM&lt;/em&gt; domain records are legit. How does it mean that they're legit, you ask?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Once a day, a &lt;a href="https://mxtoolbox.com/dmarc/details/what-do-dmarc-reports-look-like"&gt;DMARC Report&lt;/a&gt; is sent to a predefined mailbox. The report contains pass/fail attempts of &lt;em&gt;SPF&lt;/em&gt; and &lt;em&gt;DKIM&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;DMARC&lt;/em&gt; has the &lt;a href="https://dmarcian.com/policy-modes-quarantine-vs-reject/#:~:text=p=reject"&gt;reject policy&lt;/a&gt;, so if a sender attempts to send an email and both &lt;em&gt;SPF&lt;/em&gt; and &lt;em&gt;DKIM&lt;/em&gt; checks didn't pass, the receiver (recipient) would not get the message. &lt;/li&gt;
&lt;li&gt;Furthermore, &lt;em&gt;DMARC&lt;/em&gt; helps to authenticate the sender's identity since &lt;em&gt;SPF&lt;/em&gt; requires a &lt;em&gt;TXT&lt;/em&gt; record to exist in the sender's domain containing valid domains or IPs (usually of an ESP), and a &lt;em&gt;TXT&lt;/em&gt; (or &lt;em&gt;CNAME&lt;/em&gt;) record for &lt;em&gt;DKIM&lt;/em&gt; which includes a public key that was given to sender by the ESP.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's how a &lt;em&gt;DMARC Record&lt;/em&gt; looks like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Type: TXT
Name: _dmarc.meirg.co.il
Value: v=DMARC1; p=none; rua=mailto:meir+dmarc_agg@meirg.co.il, mailto:dmarc_agg@vali.email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;p=none&lt;/code&gt; is the declared policy, which is nothing. Having no policy at all is almost the same as not having a &lt;em&gt;DMARC&lt;/em&gt; record. A proper &lt;em&gt;DMARC&lt;/em&gt; record would be &lt;code&gt;p=reject&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So why am I using &lt;code&gt;p=none&lt;/code&gt;, you ask? Because I'm still not 100% sure that my outgoing email messages pass both &lt;em&gt;SPF&lt;/em&gt; and &lt;em&gt;DKIM&lt;/em&gt; by Mail eXchangers. Having &lt;code&gt;p=reject&lt;/code&gt; would block my messages completely, so for testing purposes, start with &lt;code&gt;p=none&lt;/code&gt;, and once you're sure that all your emails pass both &lt;em&gt;SPF&lt;/em&gt; and &lt;em&gt;DKIM&lt;/em&gt;, set the policy to &lt;code&gt;p=reject&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Sounds great, but how can you check all that? It sounds tough.&lt;/p&gt;

&lt;p&gt;See the &lt;code&gt;rua&lt;/code&gt; key in the code snippet above; I added &lt;code&gt;+dmarc_agg&lt;/code&gt; to my email address and &lt;a href="https://support.google.com/mail/answer/6579?hl=en#zippy=%2Ccreate-a-filter"&gt;created a filter in Gmail&lt;/a&gt;, so any message coming from &lt;em&gt;&lt;a href="mailto:meirg+dmarc_agg@meirg.co.il"&gt;meirg+dmarc_agg@meirg.co.il&lt;/a&gt;&lt;/em&gt; is tagged as &lt;code&gt;dmarc-report&lt;/code&gt;. Remember that I mentioned that a daily &lt;em&gt;DMARC&lt;/em&gt; report is sent to a "predefined email address"? that is it.&lt;/p&gt;

&lt;p&gt;But do I want to analyze &lt;em&gt;DMARC&lt;/em&gt; reports on my own? Yup, you guessed it, there's an excellent service that can do that for free, and it's called  &lt;a href="https://www.valimail.com/"&gt;ValiMail&lt;/a&gt;. ValiMail gets the daily report and analyzes it by checking how many &lt;em&gt;SPF&lt;/em&gt; and &lt;em&gt;DKIM&lt;/em&gt; successes/failures occurred in the past 24 hours and presents the summary in a friendly dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.valimail.com/partners/"&gt;ValiMail are partnered with many known organizations&lt;/a&gt;, such as Microsoft, Google, and SendGrid,  so I consider them a trusted vendor.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: The silent &lt;code&gt;dmarc_agg@vali.email&lt;/code&gt; in the &lt;code&gt;rua&lt;/code&gt; key points to ValiMail's mailbox.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  BIMI - Requires DMARC
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Definition: &lt;a href="https://tools.ietf.org/id/draft-blank-ietf-bimi-00.txt"&gt;Brand Indicators for Message Identification&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Remember with: &lt;strong&gt;B&lt;/strong&gt; for &lt;strong&gt;B&lt;/strong&gt;ragging to our audience/receivers that we're a top-notch validated sender.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reminding you that &lt;em&gt;DMARC&lt;/em&gt; has the "reject policy", so if an email sender sets it to &lt;code&gt;p=reject, pct=100&lt;/code&gt; (or &lt;code&gt;p=quarantine&lt;/code&gt;), &lt;strong&gt;ALL&lt;/strong&gt; none qualifying emails (SPF+DKIM) will not be delivered. So, cool, who enforces it? &lt;em&gt;BIMI&lt;/em&gt;! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://bimigroup.org/all-about-bimi/#:~:text=How%20do%20I%20Implement%20BIMI?"&gt;Qualifying for BIMI&lt;/a&gt; requires registering a trademark in a known patent agency and then purchasing a Verified Mark Certificate (VMC) from &lt;a href="https://www.digicert.com/tls-ssl/verified-mark-certificates"&gt;DigiCert&lt;/a&gt; or &lt;a href="https://www.entrust.com/digital-security/certificate-solutions/products/digital-certificates/verified-mark-certificates"&gt;Entrust&lt;/a&gt; So, meeting the BIMI standard is not that simple.&lt;/p&gt;

&lt;p&gt;And of course, it is possible to partially meet &lt;a href="https://bimigroup.org/bimi-for-non-trademarked-logos/"&gt;BIMI for non-trademarked logos&lt;/a&gt;. However, since &lt;em&gt;BIMI&lt;/em&gt; is still not enforced or &lt;a href="https://bimigroup.org/bimi-infographic/"&gt;widely recognized by Mail eXchangers&lt;/a&gt;, I consider it a nice-to-have, and I'll get to it once I'll implement it once I get enough data from the &lt;em&gt;DMARC&lt;/em&gt; reports.&lt;/p&gt;

&lt;p&gt;To my feeling, it'll be easier to qualify today for &lt;em&gt;BIMI&lt;/em&gt; than it will be in a few years. If you spot a fully certified &lt;em&gt;BIMI&lt;/em&gt; email sender, it means it is ... Genuine and can be trusted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mysterious MX
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Definition: &lt;a href="https://datatracker.ietf.org/doc/html/rfc974"&gt;Mail Exchanger&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Remember with: Gmail is a Mail Exchanger, Google Workspace is an ESP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, we got to MX; in case you haven't noticed, I wrote "Mail eXchangers" each time I had to mention a mail exchanger. You've just learned that &lt;em&gt;MX Record&lt;/em&gt; stands for &lt;strong&gt;M&lt;/strong&gt;ail e*&lt;em&gt;X&lt;/em&gt;*changers that are allowed to receive emails on your behalf.&lt;/p&gt;

&lt;p&gt;Since &lt;em&gt;MX Records&lt;/em&gt; are for receiving emails and not sending, see my &lt;a href="https://stackoverflow.com/questions/46113231/receiving-email-is-not-working-in-amazon-ses/62452983#62452983"&gt;stackoverflow answer&lt;/a&gt; on how to configure &lt;em&gt;MX Records&lt;/em&gt; for &lt;a href="https://aws.amazon.com/ses/"&gt;AWS Simple Email Service (SES)&lt;/a&gt; and the things that I've learned during the process.&lt;/p&gt;

&lt;p&gt;To see how an &lt;em&gt;MX Record&lt;/em&gt; looks like, check mine with &lt;a href="https://mxtoolbox.com/SuperTool.aspx?action=mx%3ameirg.co.il&amp;amp;run=toolpage"&gt;mxtoolbox.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gmail is a Mail eXchanger
&lt;/h2&gt;

&lt;p&gt;How do you send emails from your Gmail account if it's only receiving messages with a Mail eXchanger? &lt;/p&gt;

&lt;p&gt;As part of being Gmail's client, you can send emails on behalf of &lt;code&gt;@gmail.com&lt;/code&gt;. That is set behind the scenes by Gmail's engineers, so each time you send an email using Gmail's UI, or use &lt;a href="https://developers.google.com/gmail/api"&gt;Gmail's API&lt;/a&gt;, the emails that you send are signed with Gmail's signature, which includes &lt;em&gt;SPF&lt;/em&gt;, &lt;em&gt;DKIM&lt;/em&gt;, &lt;em&gt;DMARC&lt;/em&gt;. That is why your emails are not marked as spam when you email your friends and family since most Mail Exchangers treat &lt;code&gt;@gmail.com&lt;/code&gt; as a high reputation sender.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strengthen your knowledge
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Name all the standards and provide a short description
&lt;/h3&gt;

&lt;p&gt;All records refer to the email sender's domain.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SPF&lt;/strong&gt; - TXT record, which contains the allowed sources, domains or IPs, to send emails on behalf of an email sender.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DKIM&lt;/strong&gt; - TXT/CNAME record, which contains a key generated by an ESP. I consider &lt;em&gt;DKIM&lt;/em&gt; an HTTPS mechanism for emails, which uses the &lt;a href="https://en.wikipedia.org/wiki/Public-key_cryptography"&gt;public-key cryptography&lt;/a&gt; to authenticate messages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DMARC&lt;/strong&gt; - TXT record, which contains the receiver of the daily reports. This record is not just for setting the receiver, and it also means that Mail eXchangers will check this record and act according to the rules in it. So if &lt;em&gt;SPF&lt;/em&gt; and &lt;em&gt;DKIM&lt;/em&gt; is not set, some email messages might not arrive at their destination.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BIMI&lt;/strong&gt; - TXT record, which contains a URL to an SVG logo of the email sender, and a URL of the Verified Mark Certificate (VMC) that the email sender purchased. And of course, this is how email senders can &lt;strong&gt;b&lt;/strong&gt;rag that they're legit in front of their customers.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  How do I check if my domain is a trusted email sender?
&lt;/h3&gt;

&lt;p&gt;Check if your DMARC record is valid with &lt;a href="https://tools.wordtothewise.com"&gt;https://tools.wordtothewise.com&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;https://tools.wordtothewise.com/dmarc/check/DOMAIN_NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hooray for Google Workspace customers - there's a dedicated online tool for checking whether your mailing DNS records are correctly set for Google Workspace ESP; here are my results &lt;a href="https://toolbox.googleapps.com/apps/checkmx/check?domain=meirg.co.il&amp;amp;dkim_selector="&gt;meirg.co.il - Google Check MX Status&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;IMPORTANT&lt;/strong&gt;: Do not be fooled; Google Workspace's tool is valid only if you're checking Google as an ESP. If you use this service on a Sendgrid domain, it will show many errors.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  How do I read a DMARC expression?
&lt;/h3&gt;

&lt;p&gt;The syntax of how to read/write a &lt;em&gt;DMARC expression&lt;/em&gt; can be found in &lt;a href="https://datatracker.ietf.org/doc/html/rfc7489#section-6.3"&gt;RFC7489 - Domain-based Message Authentication, Reporting, and Conformance (DMARC)&lt;/a&gt;. Another excellent source for understanding the syntax is &lt;a href="https://support.google.com/a/answer/2466563?hl=en"&gt;Google's tutorial on the DMARC record format&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's inspect &lt;a href="https://tools.wordtothewise.com/dmarc/check/gmail.com"&gt;@gmail.com's DMARC record&lt;/a&gt; together&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v=DMARC1; p=none; sp=quarantine; rua=mailto:mailauth-reports@google.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;p=none - all emails, regardless if they're validated with SPF+DKIM, can get to the receiver&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;sp=quarantine - all emails sent from subdomains, e.g. &lt;code&gt;subdomain.gmail.com&lt;/code&gt; and are not signed with SPF+DKIM are marked as spam in the receiver's mailbox. &lt;code&gt;sp&lt;/code&gt; stands for subdomain-policy and overrides &lt;code&gt;p&lt;/code&gt; rules for subdomains.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;rua=mailto - It appears that Gmail is handling their DMARC reports internally since the reporting address belongs to @google.com. So I guess they have their own "analyzing and monitoring DMARC reports" system, kewl.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inpsecting &lt;a href="https://tools.wordtothewise.com/dmarc/check/yahoo.com"&gt;@yahoo.com's DMARC&lt;/a&gt; record&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v=DMARC1; p=reject; pct=100; rua=mailto:d@rua.agari.com; mailto:d@ruf.agari.com;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;p=reject - all emails that are not signed with SPF+DKIM are rejected.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;pct=100 - the percentage of messages to apply the &lt;code&gt;p&lt;/code&gt; policy. In this case, if it's not signed with SPF+DKIM, it's not getting to the receiver at all, very restrictive&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;It's fascinating to see how different enterprise-grade email senders are setting up their DMARC records. It might look like Gmail is very permissive with their &lt;code&gt;p=none&lt;/code&gt; policy, but I'm sure they have a dedicated backend that filters out spam messages before they arrive at their customers' mailboxes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus - Can Gmail customers check the sender's validity in the UI?
&lt;/h3&gt;

&lt;p&gt;Yes, yes, YES!&lt;/p&gt;

&lt;p&gt;Eventually, an email message contains text, and this text instructs the Mail eXchanger how to validate the message's sender, process referenced images to be shown in the UI, etc.&lt;/p&gt;

&lt;p&gt;Gmail made it very easy to check, click the ellipsis, and then &lt;strong&gt;Show Original&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm98pmz51gddg9yzm5cux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm98pmz51gddg9yzm5cux.png" alt="gmail-show-original-cloudflare" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That results in a very long page that contains the whole email message, though Gmail made it very easy to track the important things, such as if &lt;strong&gt;DMARC&lt;/strong&gt; is valid.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1n99igk91x7uk0w7lex0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1n99igk91x7uk0w7lex0.png" alt="cloudflare-dmarc-check" width="800" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After reading this blog post, you've just learned that checking &lt;em&gt;DMARC&lt;/em&gt; is enough since it relies on both &lt;em&gt;SPF&lt;/em&gt; and &lt;em&gt;DKIM&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dedicated IP
&lt;/h2&gt;

&lt;p&gt;Psst! Glad to see that you've read it all; This blog post is long enough, so here's a great resource that can help you decide whether to purchase a dedicated IP or not - &lt;a href="https://www.m3aawg.org/sites/default/files/document/M3AAWG_Senders_BCP_Ver3-2015-02.pdf"&gt;3.4 Shared vs. Dedicated IPs, M3AAWG Sender Best Common Practices v3.0, Feb 2015&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In short, if you're sending high volumes, &lt;a href="https://sendgrid.com/resource/how-to-send-high-volume-email-sendgrids-smart-scaling-guide-2/#:~:text=Dedicated%20IPs"&gt;hundreds of thousands of emails a month&lt;/a&gt;, then you should purchase a dedicated IP. I hate it when it all ends up with "it depends on your needs", but hey, that's true.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;I admit that it was tough and felt like there's a lot to memorize, but once you truly understand how each components works, it's pretty nice and very not intimidating as it was. So enjoy setting up your mailing DNS records. That would be all.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>email</category>
      <category>dns</category>
      <category>network</category>
    </item>
    <item>
      <title>Writing Bash Scripts Like A Pro - Part 1 - Styling Guide</title>
      <dc:creator>Meir Gabay</dc:creator>
      <pubDate>Sun, 24 Oct 2021 23:51:25 +0000</pubDate>
      <link>https://forem.com/unfor19/writing-bash-scripts-like-a-pro-part-1-styling-guide-4bin</link>
      <guid>https://forem.com/unfor19/writing-bash-scripts-like-a-pro-part-1-styling-guide-4bin</guid>
      <description>&lt;p&gt;Writing &lt;a href="https://www.gnu.org/software/bash/"&gt;Bash&lt;/a&gt; scripts can be challenging if you don't know the quirks and perks. In my mother tongue, we use the Yiddish word for quirks and perks; it's called "Shtickim" (plural of &lt;a href="https://en.wikipedia.org/wiki/Shtick"&gt;"Shtick"&lt;/a&gt;). Are you ready to learn more about Bash's "Shtickim"?&lt;/p&gt;

&lt;p&gt;This blog post is part of a series that I'm working on to preserve the knowledge for future me that forgets stuff, to assist new colleagues, and indulge programmers like you who wish to love Bash as I do. So let's begin, shall we?&lt;/p&gt;

&lt;h2&gt;
  
  
  It's A Scripting Language
&lt;/h2&gt;

&lt;p&gt;It's important to remember that Bash is a &lt;a href="https://en.wikipedia.org/wiki/Scripting_language#:~:text=A%20scripting%20language%20or%20script,at%20runtime%20rather%20than%20compiled."&gt;scripting language&lt;/a&gt;, which means it doesn't offer the standard functionalities that a &lt;a href="https://en.wikipedia.org/wiki/Programming_language#:~:text=A%20programming%20language%20is%20a,consist%20of%20instructions%20for%20computers."&gt;programming language&lt;/a&gt; has to offer, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Object-Oriented Programming is not supported natively&lt;/li&gt;
&lt;li&gt;There are no external libraries like Python's &lt;a href="https://docs.python-requests.org/en/latest/"&gt;requests&lt;/a&gt; or Node's &lt;a href="https://www.npmjs.com/package/axios"&gt;axios&lt;/a&gt;, though it is possible to use external applications such as &lt;a href="https://curl.se/"&gt;curl&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.oreilly.com/library/view/learning-php-mysql/9781449337452/ch13s06.html"&gt;Variables Typing&lt;/a&gt; is not supported, and all values are evaluated as &lt;em&gt;strings&lt;/em&gt;. However, it is possible to use numbers by using specific commands, such as &lt;a href="https://tldp.org/LDP/abs/html/comparison-ops.html"&gt;test equality with -eq&lt;/a&gt; and &lt;a href="https://tldp.org/LDP/abs/html/ops.html"&gt;increment a variable with ((VAR_NAME+1))&lt;/a&gt;. Nevertheless, there's a "weak" way of declaring variables type with the &lt;a href="https://linuxhint.com/bash_declare_command"&gt;declare command&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://linuxhint.com/associative_array_bash/"&gt;Bash's Associative array&lt;/a&gt; like Python's &lt;a href="https://www.w3schools.com/python/python_dictionaries.asp"&gt;dict&lt;/a&gt; or JavaScript's &lt;a href="https://www.w3schools.com/js/js_objects.asp"&gt;Object&lt;/a&gt; is supported from version &lt;a href="https://tldp.org/LDP/abs/html/bashver4.html"&gt;Bash v4.4&lt;/a&gt;, and it's important to remember that &lt;a href="https://tldp.org/LDP/abs/html/bashver3.html"&gt;macOS is shipped with Bash v3.2&lt;/a&gt; (we'll get to that in future blog posts of this series)&lt;/li&gt;
&lt;li&gt;There is no "source of truth" for naming convention. For example, how would you name a global variable? &lt;code&gt;Pascal_Case&lt;/code&gt;? &lt;code&gt;snake_case&lt;/code&gt;? &lt;code&gt;SCREAMING_SNAKE_CASE&lt;/code&gt;?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you already guessed, "Bash programmers" (if there is such a thing) face many challenges. The above list is merely the tip of the iceberg.&lt;/p&gt;

&lt;p&gt;Here are great blog posts that share the same feelings as I do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.tothenew.com/blog/foolproof-your-bash-script-some-best-practices/"&gt;Foolproof Your Bash Script&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kvz.io/bash-best-practices.html"&gt;Best Practices for Writing Bash Scripts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bertvv.github.io/cheat-sheets/Bash.html"&gt;Bash best practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that we've covered the fact that I'm in love with Bash, I want to share that feeling with you; here goes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Variables Naming Convention
&lt;/h2&gt;

&lt;p&gt;Here's how I name variables in my Bash scripts&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Convention&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Environment&lt;/td&gt;
&lt;td&gt;Global&lt;/td&gt;
&lt;td&gt;MY_VARIABLE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Global&lt;/td&gt;
&lt;td&gt;Global&lt;/td&gt;
&lt;td&gt;_MY_VARIABLE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local&lt;/td&gt;
&lt;td&gt;Function&lt;/td&gt;
&lt;td&gt;my_variable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In my older Bash scripts, the names of the variables were hard to interpret. Changing to this naming convention helped me a lot to understand the scope of variables and their purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Good Vibes Application
&lt;/h2&gt;

&lt;p&gt;And of course, we gotta' see some practical example, so here's how I implement the above naming convention in my &lt;code&gt;good_vibes.sh&lt;/code&gt; application.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;good_vibes.sh&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# ^ This is called a Shebang&lt;/span&gt;
&lt;span class="c"&gt;# I'll cover it in future blog posts&lt;/span&gt;


&lt;span class="c"&gt;# Global variables are initialized by Env Vars.&lt;/span&gt;
&lt;span class="c"&gt;# I'm setting a default value with "${VAR_NAME:-"DEFAULT_VALUE"}"&lt;/span&gt;
&lt;span class="nv"&gt;_USER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER_NAME&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;_USER_AGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER_AGE&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;


complement_name&lt;span class="o"&gt;(){&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Wow, &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, you have a beautiful name!"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


complement_age&lt;span class="o"&gt;(){&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$age&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"30"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Seriously &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;? I thought you were &lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;age-7&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Such a weird age, are you sure it's a number?"&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


main&lt;span class="o"&gt;(){&lt;/span&gt;
  &lt;span class="c"&gt;# The only function that is not "pure"&lt;/span&gt;
  &lt;span class="c"&gt;# This function is tightly coupled to the script&lt;/span&gt;
  complement_name &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$_USER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  complement_age &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$_USER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$_USER_AGE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="c"&gt;# Invokes the main function&lt;/span&gt;
main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;good_vibes.sh - Execution and output&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;USER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Julia"&lt;/span&gt; &lt;span class="nv"&gt;USER_AGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"36"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
bash good_vibes.sh

&lt;span class="c"&gt;# Output&lt;/span&gt;
Wow, Julia, you have a beautiful name!
Seriously Julia? I thought you were 29
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down the &lt;code&gt;good_vibes.sh&lt;/code&gt; application to a "set of rules" that can be implemented in your scripts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code block spacing
&lt;/h3&gt;

&lt;p&gt;Two (2) blank rows between each block of code make the script more readable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Indentation
&lt;/h3&gt;

&lt;p&gt;I'm using two (2) spaces, though it's totally fine to use four (4) spaces for indentation. Just make sure you're not mixing between the two.&lt;/p&gt;

&lt;h3&gt;
  
  
  Curly braces
&lt;/h3&gt;

&lt;p&gt;If it's a &lt;code&gt;${VARIABLE} concatenated with string&lt;/code&gt;, use curly braces as it makes it easier to read.&lt;/p&gt;

&lt;p&gt;In case it's a &lt;code&gt;"$LONELY_VARIABLE"&lt;/code&gt; there's no need for that, as it will help you realize faster if it's "lonely" or not.&lt;/p&gt;

&lt;p&gt;The primary purpose for curly braces is for performing a &lt;a href="https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html"&gt;Shell Parameter Expansion&lt;/a&gt;, as demonstrated in the Global variables initialization part.&lt;/p&gt;

&lt;h3&gt;
  
  
  Squared brackets
&lt;/h3&gt;

&lt;p&gt;Using &lt;strong&gt;double&lt;/strong&gt; &lt;code&gt;[[ ]]&lt;/code&gt; squared brackets makes it easier to read conditional code blocks. However, do note that using double squared brackets is not supported in &lt;a href="https://stackoverflow.com/a/5725402/5285732"&gt;Shell sh&lt;/a&gt;; instead, you should use single brackets &lt;code&gt;[ ]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To demonstrate the readability, here's a "complex" conditional code block:&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="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Julia"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Willy"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="se"&gt;\&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="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER_AGE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"30"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Easy to read right?"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Mind that `||` is replaced with `-o`, see https://acloudguru.com/blog/engineering/conditions-in-bash-scripting-if-statements&lt;/span&gt;
&lt;span class="c"&gt;# Thank you William Pursell&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Julia"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Willy"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="se"&gt;\&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="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER_AGE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"30"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"No idea why but I feel lost with single brackets."&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In case you didn't notice, you've just learned that &lt;code&gt;||&lt;/code&gt; stands for &lt;code&gt;OR&lt;/code&gt; and &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; stands for &lt;code&gt;AND&lt;/code&gt;. And the short &lt;a href="https://tldp.org/LDP/abs/html/comparison-ops.html"&gt;-gt&lt;/a&gt; expression means &lt;code&gt;greater than&lt;/code&gt; when using numbers. Finally, the &lt;code&gt;\&lt;/code&gt; character allows &lt;a href="https://unix.stackexchange.com/questions/281309/shell-syntax-how-to-correctly-use-to-break-lines"&gt;breaking rows&lt;/a&gt; in favor of making the code more readable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Shtick&lt;/strong&gt;: Using &lt;code&gt;\&lt;/code&gt; with an extra space &lt;code&gt;\ &amp;lt;- extra space&lt;/code&gt; can lead to weird errors. Make sure there are no trailing spaces after &lt;code&gt;\&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I assume that using &lt;code&gt;[[ ]]&lt;/code&gt; feels more intuitive since most conditional commands are doubled &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; &lt;code&gt;||&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Variable initialization
&lt;/h3&gt;

&lt;p&gt;Global variables are initialized with Environment Variables and are set with default values in case of empty Environment variables.&lt;/p&gt;

&lt;p&gt;As mentioned in the &lt;code&gt;good_vibes.sh&lt;/code&gt; comments, I'm setting a default value with&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="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;VAR_NAME&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="s2"&gt;"DEFAULT_VALUE"&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above snippet, the text &lt;code&gt;DEFAULT_VALUE&lt;/code&gt; is hardcoded, and it's possible to replace it with a variable. For example&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;_USER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER_NAME&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Functions and local function variables
&lt;/h3&gt;

&lt;p&gt;Functions names and &lt;code&gt;local&lt;/code&gt; function variables names are &lt;code&gt;snake_cased&lt;/code&gt;. You might want to change functions names to &lt;code&gt;lowerCamelCase&lt;/code&gt;, and of course, it's your call.&lt;/p&gt;

&lt;p&gt;Coupling a function to the script is a common mistake, though I do sin from time to time, and you'll see Global/Environment variables in my functions, but that happens when I know that "this piece of code won't change a lot". &lt;/p&gt;

&lt;p&gt;Oh, and make sure you don't use &lt;code&gt;$1&lt;/code&gt; or any other argument directly; always use &lt;code&gt;local var_name="$1"&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;_USER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER_NAME&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Bad - coupled&lt;/span&gt;
coupled_username&lt;span class="o"&gt;(){&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"_USER_NAME = &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_USER_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Good - decoupled&lt;/span&gt;
decoupled_username&lt;span class="o"&gt;(){&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"name = &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Usage&lt;/span&gt;
coupled_username  
decoupled_username &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$_USER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Functional Programming
&lt;/h3&gt;

&lt;p&gt;This topic relates to &lt;strong&gt;Functions and local function variables&lt;/strong&gt;, where functions are as "pure" as possible. As you can see in &lt;code&gt;good_vibes.sh&lt;/code&gt;, almost everything is wrapped in a function, except for &lt;strong&gt;Initializing Global variables&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I don't see the point of writing the &lt;code&gt;init_vars&lt;/code&gt; function, whose purpose is to deal with Global variables. However, I do find myself adding a &lt;code&gt;validate_vars&lt;/code&gt; function from time to time, which goes over the Global variables and validates their values. I'm sure there's room for debate here, so feel free to comment with your thoughts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;The "Good Vibes Application" mostly covered how to write a readable Bash script following the &lt;a href="https://en.wikipedia.org/wiki/Functional_programming"&gt;Functional Programming&lt;/a&gt; paradigm.&lt;/p&gt;

&lt;p&gt;If you feel that there's a need to change how you name variables and functions, go for it! As long as it's easy to understand your code, you're on the right track.&lt;/p&gt;

&lt;p&gt;The next blog posts in this series will cover the following topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Error handling&lt;/li&gt;
&lt;li&gt;Retrieving JSON data from an HTTP endpoint&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tldp.org/LDP/abs/html/x9644.html"&gt;Background jobs&lt;/a&gt; and watching file for changes with &lt;a href="https://github.com/emcrisostomo/fswatch"&gt;fswatch&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Git Repository structure - adding Bash scripts to existing repositories or creating a new repository with a Bash CLI application&lt;/li&gt;
&lt;li&gt;Publishing a Bash CLI as a &lt;a href="https://www.docker.com/why-docker"&gt;Docker&lt;/a&gt; image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And more, and more ... I'm just going to spit it all out to blog posts. Feel free to comment with questions or suggestions for my next blog posts.&lt;/p&gt;

</description>
      <category>bash</category>
      <category>programming</category>
      <category>tutorials</category>
      <category>devops</category>
    </item>
    <item>
      <title>Vue3 + Quasar 2.1 + TypeScript Sample CRUD Application Project</title>
      <dc:creator>Meir Gabay</dc:creator>
      <pubDate>Mon, 11 Oct 2021 22:34:07 +0000</pubDate>
      <link>https://forem.com/unfor19/vue3-quasar-2-1-typescript-sample-crud-application-project-ld2</link>
      <guid>https://forem.com/unfor19/vue3-quasar-2-1-typescript-sample-crud-application-project-ld2</guid>
      <description>&lt;p&gt;Hi all,&lt;br&gt;
I've been working on a Vue3 + Quasar 2.1 + TypeScript CRUD application.&lt;br&gt;
I'm using &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/APIReference/Welcome.html"&gt;AWS SSM Parameters API&lt;/a&gt; to test the application and it is being served with &lt;a href="https://github.com/localstack/localstack"&gt;localstack&lt;/a&gt; so I can test the application locally. In case it's not clear, AWS SSM Parameters (served by localstack) is my local "backend server".&lt;/p&gt;

&lt;p&gt;To run the application and test it immediately, you need to have &lt;a href="https://docs.docker.com/get-docker/"&gt;Docker&lt;/a&gt; and &lt;a href="https://docs.docker.com/compose/install/"&gt;Docker-Compose&lt;/a&gt; installed - that's it.&lt;/p&gt;

&lt;p&gt;Project link @ GitHub - &lt;a href="https://github.com/unfor19/aws-webui"&gt;https://github.com/unfor19/aws-webui&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sidenote- I'm not a frontend developer, so it was quite tough, but eventually, it was a great experience.&lt;/p&gt;

&lt;p&gt;Feel free to ask any questions&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>vue</category>
      <category>quasar</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
