<?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: augusthottie</title>
    <description>The latest articles on Forem by augusthottie (@augusthottie).</description>
    <link>https://forem.com/augusthottie</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%2F1713210%2Ffac3c0eb-0bc1-4abe-bd08-eedbf1072d8c.jpeg</url>
      <title>Forem: augusthottie</title>
      <link>https://forem.com/augusthottie</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/augusthottie"/>
    <language>en</language>
    <item>
      <title>I Built a Serverless Event-Driven Pipeline on AWS</title>
      <dc:creator>augusthottie</dc:creator>
      <pubDate>Mon, 06 Apr 2026 16:31:27 +0000</pubDate>
      <link>https://forem.com/augusthottie/i-built-a-serverless-event-driven-pipeline-on-aws-3d05</link>
      <guid>https://forem.com/augusthottie/i-built-a-serverless-event-driven-pipeline-on-aws-3d05</guid>
      <description>&lt;p&gt;After five projects deep on containers and Kubernetes, I needed to add range to my portfolio. Every DevOps role I've looked at mentions serverless somewhere, Lambda for glue code, API Gateway for webhooks, DynamoDB for low-latency lookups, SQS for decoupling services. So this week I built a serverless event-driven pipeline from scratch.&lt;/p&gt;

&lt;p&gt;The use case is a URL shortener with click analytics, but the architecture pattern is the same one you'd use for payment processing, IoT ingestion, audit logging, or any event-driven system. The interesting part isn't the URL shortener, it's the async decoupling, the atomic operations, the failure handling, and the IAM patterns.&lt;/p&gt;

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

&lt;p&gt;A four-Lambda pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Shortener&lt;/strong&gt; (&lt;code&gt;POST /shorten&lt;/code&gt;): generates a short code, writes to DynamoDB with a conditional write to handle collisions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redirect&lt;/strong&gt; (&lt;code&gt;GET /{code}&lt;/code&gt;): reads the URL, fire-and-forget sends a click event to SQS, returns a 302 in under 100ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics&lt;/strong&gt; (SQS-triggered): processes click events in batches of 10, writes details to a clicks table, atomically increments the counter on the urls table&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stats&lt;/strong&gt; (&lt;code&gt;GET /stats/{code}&lt;/code&gt;): queries both tables, aggregates top user agents and referers, returns JSON&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All Python 3.12, all behind API Gateway HTTP API v2, all defined as Terraform with reusable modules. 38 resources total. Deploys in under two minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /shorten      →  Lambda (shortener)  →  DynamoDB (urls table)

GET /{code}        →  Lambda (redirect)   →  DynamoDB → SQS → 302
                                                         ↓
                                              Lambda (analytics)
                                                         ↓
                                              DynamoDB (clicks + counter)

GET /stats/{code}  →  Lambda (stats)      →  DynamoDB (both tables)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight is the SQS queue between the redirect and analytics Lambdas. The redirect Lambda doesn't wait for analytics processing, it sends a message to SQS and immediately returns the 302. Whether analytics takes 10ms or 10 seconds, users get redirected instantly. Click event processing happens entirely in the background.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Patterns That Make This Production-Realistic
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Conditional writes for collision handling.&lt;/strong&gt; The shortener generates a 7-character random code, but two simultaneous requests could generate the same one. DynamoDB's &lt;code&gt;ConditionExpression: "attribute_not_exists(code)"&lt;/code&gt; makes the write fail atomically if the code already exists. Combined with retry logic, this is collision-safe at any concurrency level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Atomic counters with UpdateExpression.&lt;/strong&gt; When the analytics Lambda increments the click count, it uses &lt;code&gt;UpdateExpression: "ADD clicks :inc"&lt;/code&gt;. This is atomic at the database level, no read-modify-write race conditions. If 100 clicks come in simultaneously, all 100 get counted correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Partial batch failure for SQS.&lt;/strong&gt; The default SQS → Lambda trigger fails the entire batch if any message errors. That's wasteful and creates retry storms. By setting &lt;code&gt;function_response_types = ["ReportBatchItemFailures"]&lt;/code&gt; and returning &lt;code&gt;{"batchItemFailures": [{"itemIdentifier": messageId}]}&lt;/code&gt; from the Lambda, only the failed messages go back to the queue. The successful 9 out of 10 stay processed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dead Letter Queue with 3-retry redrive.&lt;/strong&gt; Failed messages get retried 3 times before being moved to a dead letter queue. The DLQ holds them for 14 days so you can investigate without blocking the main queue. This is the difference between "the pipeline is broken" and "we have visibility into 12 failed messages from yesterday."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Least-privilege IAM per Lambda.&lt;/strong&gt; Each function has its own role with the minimum permissions it needs. The shortener can only &lt;code&gt;PutItem&lt;/code&gt; on the urls table. The redirect can only &lt;code&gt;GetItem&lt;/code&gt; on urls and &lt;code&gt;SendMessage&lt;/code&gt; to SQS. The analytics Lambda can read from SQS and write to both tables. The stats Lambda can only read from both tables. If any function gets compromised, the blast radius is contained.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Terraform Modules
&lt;/h2&gt;

&lt;p&gt;I built four reusable modules:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;lambda/&lt;/code&gt;&lt;/strong&gt; takes a source directory, handler name, IAM policy JSON, and environment variables. It packages the code into a zip, creates an IAM role, attaches the basic execution policy plus the custom one, creates the function, and sets up a CloudWatch log group with 14-day retention. Adding a fifth Lambda would be 15 lines of root-level code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;dynamodb/&lt;/code&gt;&lt;/strong&gt; creates the urls and clicks tables. The clicks table has a Global Secondary Index on &lt;code&gt;(code, timestamp)&lt;/code&gt; so the stats Lambda can query recent clicks efficiently without scanning the whole table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;sqs/&lt;/code&gt;&lt;/strong&gt; creates the main queue and the dead letter queue, with the redrive policy linking them. Visibility timeout is 60 seconds (must be at least the Lambda timeout), long polling is enabled for 20 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;api_gateway/&lt;/code&gt;&lt;/strong&gt; creates an HTTP API v2 (cheaper than REST API), three integrations, three routes (&lt;code&gt;POST /shorten&lt;/code&gt;, &lt;code&gt;GET /{code}&lt;/code&gt;, &lt;code&gt;GET /stats/{code}&lt;/code&gt;), the auto-deploy stage with access logging, and the Lambda permissions allowing API Gateway to invoke each function.&lt;/p&gt;

&lt;p&gt;The root &lt;code&gt;main.tf&lt;/code&gt; composes them and passes the outputs between modules. There's also a &lt;code&gt;null_resource&lt;/code&gt; with &lt;code&gt;local-exec&lt;/code&gt; that copies the shared utilities folder into each Lambda's source directory before packaging — Lambda doesn't have a native way to share source code between functions without using Layers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Live Demo
&lt;/h2&gt;

&lt;p&gt;I built an interactive HTML page that calls the live API directly so you don't have to take my word for it. There's a "Shorten URL" button, a "Generate 10 Clicks" button, and a "Fetch Stats" button. There's a real-time event log at the bottom showing every request as it happens.&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%2Fltcra6arhrnu9eu54id0.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fltcra6arhrnu9eu54id0.jpeg" alt="Demo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The whole demo is a single HTML file with no build step. CSS uses a Fraunces serif display font paired with JetBrains Mono, an orange accent on a dark grid background, and a noise overlay for texture. It looks like a developer tool, not a generic landing page.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Debugging That Taught Me the Most
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;HTTP API v2 payload format is different.&lt;/strong&gt; I had a shared &lt;code&gt;log_event()&lt;/code&gt; helper that read &lt;code&gt;event.get("httpMethod")&lt;/code&gt; from API Gateway. That worked fine in REST API v1, but in HTTP API v2 the method is at &lt;code&gt;event["requestContext"]["http"]["method"]&lt;/code&gt;. The result was that every log entry showed &lt;code&gt;"event_type": "sqs"&lt;/code&gt; even for HTTP requests, because &lt;code&gt;httpMethod&lt;/code&gt; was missing and the default was "sqs". Subtle bug, easy to miss until you're trying to debug something else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lambda cold starts cause race conditions in test scripts.&lt;/strong&gt; My test script does &lt;code&gt;POST /shorten&lt;/code&gt; immediately followed by &lt;code&gt;GET /{code}&lt;/code&gt;. If the redirect Lambda is cold, the first GET happens before the Lambda has finished initializing, and somehow this causes API Gateway to return a 404 without invoking the Lambda. I confirmed this by checking CloudWatch logs: no log entries for the failed requests. Adding a 1-2 second delay between the shorten and the first redirect fixed it. In production this isn't an issue because Lambdas stay warm under continuous load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Partial batch failure requires opt-in.&lt;/strong&gt; I assumed SQS → Lambda would handle failures at the message level by default. It doesn't. You have to explicitly set &lt;code&gt;function_response_types = ["ReportBatchItemFailures"]&lt;/code&gt; on the event source mapping AND return the failed message IDs in the right format from your Lambda. Without both, one bad message fails the entire batch and you get retry storms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shared code in Lambda is harder than it should be.&lt;/strong&gt; Python doesn't have a clean way to share modules between Lambda functions without using Layers (which add deployment complexity). I ended up using a Terraform &lt;code&gt;null_resource&lt;/code&gt; with &lt;code&gt;local-exec&lt;/code&gt; to copy &lt;code&gt;src/shared/&lt;/code&gt; into each function's source directory before packaging. Hacky but effective.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cost Comparison
&lt;/h2&gt;

&lt;p&gt;Running this pipeline with 1 million requests per month costs approximately $3.60. That includes API Gateway, all four Lambdas, DynamoDB on-demand, SQS, and CloudWatch Logs.&lt;/p&gt;

&lt;p&gt;For context, my EKS cluster from Projects 3, 4, and 5 costs about $213 per month, and that's whether it's serving zero requests or a million. Serverless is genuinely cheaper for event-driven workloads, especially during development when traffic is sporadic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters for My Portfolio
&lt;/h2&gt;

&lt;p&gt;Before this project, my portfolio was container-heavy. Five projects on EKS, ECS, Helm, ArgoCD. Strong on Kubernetes, weak on serverless. This adds a completely different dimension, event-driven architecture, async processing, NoSQL design, reusable IaC modules, and security patterns specific to AWS managed services.&lt;/p&gt;

&lt;p&gt;In an interview, if someone asks "tell me about a serverless project," I now have a 90-second answer that hits async decoupling, atomic operations, partial batch failures, dead letter queues, and least-privilege IAM. And I have a live demo URL they can click.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub repo&lt;/strong&gt;: &lt;a href="https://github.com/augusthottie/aws-serverless-event-pipeline" rel="noopener noreferrer"&gt;aws-serverless-event-pipeline&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live demo&lt;/strong&gt;: &lt;a href="https://augusthottie.github.io/aws-serverless-event-pipeline/" rel="noopener noreferrer"&gt;augusthottie.com/serverless-demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project 5 (Loki)&lt;/strong&gt;: &lt;a href="https://dev.to/augusthottie/i-added-log-aggregation-to-my-eks-observability-stack-metrics-logs-in-one-dashboard-3i3a"&gt;Blog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project 4 (Observability)&lt;/strong&gt;: &lt;a href="https://dev.to/augusthottie/i-added-prometheus-grafana-and-custom-alerting-to-my-eks-cluster-heres-how-observability-59n3"&gt;Blog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project 3 (GitOps on EKS)&lt;/strong&gt;: &lt;a href="https://dev.to/augusthottie/i-set-up-gitops-on-eks-with-argocd-heres-what-kubernetes-actually-looks-like-in-production-1961"&gt;Blog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio&lt;/strong&gt;: &lt;a href="https://www.augusthottie.com/" rel="noopener noreferrer"&gt;augusthottie.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Six projects deep into my DevOps portfolio ahead of the AWS DevOps Professional certification. Connect on &lt;a href="https://www.linkedin.com/in/jessica-chioma-chimex/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, I'd love to hear what you're building.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>lambda</category>
      <category>devops</category>
    </item>
    <item>
      <title>I Added Log Aggregation to My EKS Observability Stack, Metrics + Logs in One Dashboard</title>
      <dc:creator>augusthottie</dc:creator>
      <pubDate>Wed, 01 Apr 2026 17:32:43 +0000</pubDate>
      <link>https://forem.com/augusthottie/i-added-log-aggregation-to-my-eks-observability-stack-metrics-logs-in-one-dashboard-3i3a</link>
      <guid>https://forem.com/augusthottie/i-added-log-aggregation-to-my-eks-observability-stack-metrics-logs-in-one-dashboard-3i3a</guid>
      <description>&lt;p&gt;Last week I built an observability stack with Prometheus, Grafana, and custom alerting on EKS. The LinkedIn post got more engagement than anything I'd posted before, and two comments suggested the same thing: "Integrate Loki for logs."&lt;/p&gt;

&lt;p&gt;They were right. Metrics tell you &lt;em&gt;that&lt;/em&gt; something is wrong. Logs tell you &lt;em&gt;why&lt;/em&gt;. Without both in the same place, you're switching between &lt;code&gt;kubectl logs&lt;/code&gt; and Grafana dashboards trying to correlate timestamps manually. That's not a workflow, that's a scavenger hunt.&lt;/p&gt;

&lt;p&gt;So I added Loki.&lt;/p&gt;

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

&lt;p&gt;Loki and Promtail, deployed via ArgoCD alongside the existing Prometheus stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Promtail&lt;/strong&gt; runs as a DaemonSet on every node, tailing container logs from &lt;code&gt;/var/log/pods&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loki&lt;/strong&gt; stores and indexes the logs, queryable via LogQL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grafana&lt;/strong&gt; gets a new "Logs &amp;amp; Metrics Correlation" dashboard with metrics and logs side by side&lt;/li&gt;
&lt;li&gt;A new &lt;strong&gt;Loki datasource&lt;/strong&gt; in Grafana so both Prometheus and Loki are available in the same dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entire addition was three files: an ArgoCD application for the Loki stack, a Grafana datasource ConfigMap, and a logs dashboard ConfigMap. Push to main, ArgoCD syncs, done.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Logs &amp;amp; Metrics Correlation Dashboard
&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%2Fb7b1wifp0u7laedju0k2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb7b1wifp0u7laedju0k2.png" alt="Loki" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the dashboard I wish I'd had from the start. Seven panels in four rows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Row 1: Metrics&lt;/strong&gt;: API Request Rate and Error Rate from Prometheus. See the traffic pattern and spot anomalies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Row 2: API Logs&lt;/strong&gt;: Live log stream from the gitops-api containers via Loki. When you see a spike in the metrics above, scroll down and the logs from that exact time range are right there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Row 3: Infrastructure Logs&lt;/strong&gt;: PostgreSQL logs on the left, Redis logs on the right. Database checkpoint warnings, connection events, cache operations, all visible without running &lt;code&gt;kubectl logs&lt;/code&gt; across multiple pods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Row 4: Error Logs&lt;/strong&gt;: A filtered view showing only lines matching &lt;code&gt;error&lt;/code&gt;, &lt;code&gt;fail&lt;/code&gt;, &lt;code&gt;panic&lt;/code&gt;, &lt;code&gt;crash&lt;/code&gt;, or &lt;code&gt;exception&lt;/code&gt; across all containers. This is the "something is broken, show me what" panel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Row 5: Log Volume&lt;/strong&gt;: Lines per second per container. A sudden spike in log volume often means something is throwing errors in a loop.&lt;/p&gt;

&lt;p&gt;The key insight: &lt;strong&gt;time-synced panels&lt;/strong&gt;. When you drag to select a time range on the metrics graph, the log panels update to show logs from that exact window. That's the metric-to-log correlation workflow, see a spike, select the time range, read the logs. Root cause in under two minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  LogQL: The Query Language
&lt;/h2&gt;

&lt;p&gt;If you know PromQL, LogQL feels familiar. Stream selectors use curly braces like Prometheus label matchers:&lt;/p&gt;

&lt;p&gt;All logs from the three-tier namespace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;namespace=&lt;/span&gt;&lt;span class="s2"&gt;"three-tier"&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;Just the API container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;namespace=&lt;/span&gt;&lt;span class="s2"&gt;"three-tier"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;container=&lt;/span&gt;&lt;span class="s2"&gt;"gitops-api"&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;Filter for errors using a pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{namespace="three-tier"} |~ "(?i)(error|fail|panic|crash|exception)"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Log volume as a metric (for the timeseries panel):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sum(rate({namespace="three-tier"}[5m])) by (container)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last one is interesting: &lt;code&gt;rate()&lt;/code&gt; on a log stream gives you lines per second, which you can graph just like a Prometheus metric. Useful for spotting error storms.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Went Wrong (And What I Learned)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Node Capacity
&lt;/h3&gt;

&lt;p&gt;Loki wouldn't schedule. The two t3.medium nodes were already running the app, Prometheus, Grafana, Alertmanager, Node Exporter, kube-state-metrics, ArgoCD, cert-manager, and the LB controller. Too many pods. I had to scale the node group to 3 nodes before Loki could start.&lt;/p&gt;

&lt;p&gt;This is something you don't think about until it happens, t3.medium supports around 17 pods per node, and a monitoring stack eats through that fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  EBS CSI Driver (Again)
&lt;/h3&gt;

&lt;p&gt;The Loki PVC was stuck in Pending even after adding a third node. The EBS CSI driver's IAM role still had the old cluster's OIDC provider URL. Third time hitting this issue — by now the fix is muscle memory: delete the IAM service account, recreate it, reinstall the addon with &lt;code&gt;--resolve-conflicts OVERWRITE&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grafana Datasource Provisioning
&lt;/h3&gt;

&lt;p&gt;The Loki datasource ConfigMap existed in Kubernetes but Grafana's sidecar didn't pick it up. After a restart, the Prometheus datasource also disappeared. I ended up adding both datasources manually through the Grafana UI.&lt;/p&gt;

&lt;p&gt;The lesson: Grafana's sidecar provisioning is convenient when it works, but when it doesn't, just add datasources manually and move on. The dashboards are what matter, not how the datasource was configured.&lt;/p&gt;

&lt;h3&gt;
  
  
  Curly Quotes in Dashboard JSON
&lt;/h3&gt;

&lt;p&gt;When importing dashboard JSON through Grafana's UI, the PromQL queries had corrupted quotes, curly/smart quotes instead of straight quotes. Every panel showed a parse error. The fix was to edit each panel and retype the query manually from the keyboard.&lt;/p&gt;

&lt;p&gt;This is a subtle one. If you copy-paste JSON through a text editor or chat that auto-converts quotes, your Grafana panels will break silently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Logs Complete the Observability Story
&lt;/h2&gt;

&lt;p&gt;With just Prometheus, my monitoring answer was: "The error rate spiked at 3:42 PM." With Loki added, it becomes: "The error rate spiked at 3:42 PM because PostgreSQL was restarting after an OOM kill, here are the exact log lines."&lt;/p&gt;

&lt;p&gt;That's the difference between detecting a problem and diagnosing it. In an interview, being able to describe a workflow that goes from alert → metric → log → root cause shows you've actually operated production systems, not just set up dashboards.&lt;/p&gt;

&lt;p&gt;The full observability stack now covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Metrics&lt;/strong&gt;: Prometheus + custom application instrumentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs&lt;/strong&gt;: Loki + Promtail collecting from every container&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alerting&lt;/strong&gt;: PrometheusRules with 9 custom alerts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dashboards&lt;/strong&gt;: Two Grafana dashboards, one for metrics, one for metric-to-log correlation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All deployed via GitOps&lt;/strong&gt;: ArgoCD managing four applications from a single repo&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub repo&lt;/strong&gt;: &lt;a href="https://github.com/augusthottie/gitops-eks" rel="noopener noreferrer"&gt;gitops-eks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project 4 (Observability)&lt;/strong&gt;: &lt;a href="https://dev.to/augusthottie/i-added-prometheus-grafana-and-custom-alerting-to-my-eks-cluster-heres-how-observability-59n3"&gt;Blog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project 3 (GitOps on EKS)&lt;/strong&gt;: &lt;a href="https://dev.to/augusthottie/i-set-up-gitops-on-eks-with-argocd-heres-what-kubernetes-actually-looks-like-in-production-1961"&gt;Blog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio&lt;/strong&gt;: &lt;a href="https://www.augusthottie.com/" rel="noopener noreferrer"&gt;augusthottie.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Building my DevOps portfolio ahead of the AWS DevOps Professional certification. Connect on &lt;a href="https://www.linkedin.com/in/jessica-chioma-chimex/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, I'd love to hear what you're building.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>observability</category>
      <category>terraform</category>
    </item>
    <item>
      <title>I Added Prometheus, Grafana, and Custom Alerting to My EKS Cluster, Here's How Observability Actually Works</title>
      <dc:creator>augusthottie</dc:creator>
      <pubDate>Wed, 25 Mar 2026 11:41:47 +0000</pubDate>
      <link>https://forem.com/augusthottie/i-added-prometheus-grafana-and-custom-alerting-to-my-eks-cluster-heres-how-observability-59n3</link>
      <guid>https://forem.com/augusthottie/i-added-prometheus-grafana-and-custom-alerting-to-my-eks-cluster-heres-how-observability-59n3</guid>
      <description>&lt;p&gt;After building three projects: a CI/CD pipeline, a 3-tier architecture, and GitOps on EKS, I had one obvious gap: observability. I could deploy things, but I couldn't answer "is it healthy?" beyond checking if pods were running.&lt;/p&gt;

&lt;p&gt;"How do you monitor your services?" is an interview question I wasn't ready for. I'd used Grafana dashboards other people built. I'd looked at CloudWatch metrics someone else configured. But I'd never instrumented an application, written PromQL queries, or set up alerting rules from scratch.&lt;/p&gt;

&lt;p&gt;So I did all of it.&lt;/p&gt;

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

&lt;p&gt;I took the GitOps EKS project from last week and added a complete observability layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instrumented the Node.js API with &lt;code&gt;prom-client&lt;/code&gt;, 7 custom metrics covering HTTP requests, database queries, cache operations, and connection pools&lt;/li&gt;
&lt;li&gt;Deployed kube-prometheus-stack (Prometheus, Grafana, Alertmanager, Node Exporter, kube-state-metrics) via ArgoCD&lt;/li&gt;
&lt;li&gt;Built a 9-panel Grafana dashboard showing request rate, error rate, latency, cache hit/miss ratio, DB query performance, and pod resources&lt;/li&gt;
&lt;li&gt;Wrote 9 custom alert rules across API health, database performance, cache efficiency, and pod stability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything deployed via GitOps, push to main, ArgoCD syncs the monitoring stack and custom configs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instrumenting the Application
&lt;/h2&gt;

&lt;p&gt;The first step was making the app emit metrics. I added &lt;code&gt;prom-client&lt;/code&gt; and created a metrics module with:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTTP middleware&lt;/strong&gt; that wraps every request, tracking method, route, status code, and duration. The &lt;code&gt;/metrics&lt;/code&gt; endpoint itself is excluded so Prometheus scraping doesn't inflate the numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database helpers&lt;/strong&gt; that time every query and track success/failure by operation type (select, insert, delete). This means I can see not just "is the database slow?" but "are inserts slower than selects?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache tracking&lt;/strong&gt; on every Redis operation, get (hit or miss), set, and invalidate. This shows whether the caching layer is actually working or if every request hits the database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connection pool gauge&lt;/strong&gt; that samples active database connections every 5 seconds. When this approaches the pool limit (10), something is holding connections open.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/metrics&lt;/code&gt; endpoint exposes everything in Prometheus text format. Hit it once and you get counters, histograms, and gauges, about 100 lines of metrics per scrape.&lt;/p&gt;

&lt;h2&gt;
  
  
  ServiceMonitor: The Right Way to Scrape
&lt;/h2&gt;

&lt;p&gt;My first attempt used &lt;code&gt;additionalScrapeConfigs&lt;/code&gt; in the Prometheus values, a raw scrape config injected into the Prometheus config. It didn't work. The operator didn't pick it up, and debugging why was a dead end.&lt;/p&gt;

&lt;p&gt;The correct approach is a &lt;strong&gt;ServiceMonitor&lt;/strong&gt; — a Kubernetes CRD that tells the Prometheus operator what to scrape. It uses label selectors to find Services and endpoints automatically. Mine looks for any Service with &lt;code&gt;app: gitops-api&lt;/code&gt; in the &lt;code&gt;three-tier&lt;/code&gt; namespace, scrapes port &lt;code&gt;http&lt;/code&gt; on path &lt;code&gt;/metrics&lt;/code&gt; every 15 seconds.&lt;/p&gt;

&lt;p&gt;One detail that took me longer than I'd like to admit: the Service needs a &lt;strong&gt;named port&lt;/strong&gt;. Not just &lt;code&gt;port: 80&lt;/code&gt; but &lt;code&gt;name: http, port: 80&lt;/code&gt;. The ServiceMonitor references the port by name, and without it, Prometheus silently ignores the target.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dashboard
&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%2Fu2s6tzpt636k4us9k7rm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu2s6tzpt636k4us9k7rm.png" alt="Grafana-Dashboard" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built the dashboard as a JSON ConfigMap deployed via ArgoCD. Nine panels in three rows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Row 1: HTTP layer:&lt;/strong&gt;&lt;br&gt;
Request Rate (per route), Error Rate (percentage of 5xx responses), P95 Latency (per route). These tell you if the API is serving traffic, how much is failing, and how fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Row 2: Data layer:&lt;/strong&gt;&lt;br&gt;
Requests by Status (pie chart showing 200/201/404/503 distribution), Cache Hit/Miss (pie chart — green means Redis is working), DB Query Duration (p95 for inserts vs selects), DB Active Connections (gauge per pod, 0-10 scale with yellow at 6, red at 8).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Row 3: Infrastructure:&lt;/strong&gt;&lt;br&gt;
DB Queries per Second (insert and select rates), Pod Memory Usage, Pod CPU Usage. These show whether the workload needs more resources.&lt;/p&gt;

&lt;p&gt;The moment all 9 panels lit up with real data was genuinely satisfying. The error rate panel showed a real 503 spike from when PostgreSQL was still starting, that's not test data, that's the actual system behavior captured in metrics.&lt;/p&gt;
&lt;h2&gt;
  
  
  The PromQL Behind Each Panel
&lt;/h2&gt;

&lt;p&gt;For anyone building their own dashboard, here are the exact queries:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request Rate&lt;/strong&gt;: requests per second, broken down by route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http_requests_total&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5m&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Error Rate&lt;/strong&gt;: percentage of 5xx responses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http_requests_total&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=~&lt;/span&gt;&lt;span class="s2"&gt;"5.."&lt;/span&gt;&lt;span class="p"&gt;}[&lt;/span&gt;&lt;span class="mi"&gt;5m&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; 
&lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http_requests_total&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5m&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;P95 Latency&lt;/strong&gt;: 95th percentile response time per route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="nb"&gt;histogram_quantile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http_request_duration_seconds_bucket&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5m&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;le&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cache Hit/Miss&lt;/strong&gt;: Redis cache effectiveness over the last hour:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;increase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache_operations_total&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"get"&lt;/span&gt;&lt;span class="p"&gt;}[&lt;/span&gt;&lt;span class="mi"&gt;1h&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;DB Query Duration (p95)&lt;/strong&gt;: insert vs select latency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="nb"&gt;histogram_quantile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_query_duration_seconds_bucket&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5m&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;le&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;DB Queries per Second&lt;/strong&gt;: operation throughput:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_queries_total&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5m&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pod Memory&lt;/strong&gt;: working set per container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="n"&gt;container_memory_working_set_bytes&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"three-tier"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pod CPU&lt;/strong&gt;: usage rate per pod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;container_cpu_usage_seconds_total&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"three-tier"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;}[&lt;/span&gt;&lt;span class="mi"&gt;5m&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;pod&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key thing to understand: &lt;code&gt;rate()&lt;/code&gt; calculates per-second averages over a window, while &lt;code&gt;increase()&lt;/code&gt; gives you the total count over a window. Use &lt;code&gt;rate()&lt;/code&gt; for time-series graphs, &lt;code&gt;increase()&lt;/code&gt; for pie charts and totals. And &lt;code&gt;histogram_quantile()&lt;/code&gt; is how you get percentiles from histogram buckets, you can't just average latency and get useful numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alert Rules
&lt;/h2&gt;

&lt;p&gt;I wrote 9 PrometheusRules covering the scenarios I'd actually want to be woken up for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API Health:&lt;/strong&gt; Error rate above 5% (critical), P95 latency above 1 second (warning), metrics endpoint unreachable (critical). The error rate alert uses a 2-minute &lt;code&gt;for&lt;/code&gt; duration so a single failed request doesn't trigger it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database:&lt;/strong&gt; Query error rate above 1% (critical), P95 query time above 500ms (warning), connection pool above 8/10 active (warning). The connection pool alert is the early warning — if you're at 80% capacity, the next traffic spike will exhaust it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache:&lt;/strong&gt; Miss rate above 80% for 10 minutes (warning). A high miss rate means either Redis is down, the cache TTL is too short, or the data is never being cached. The 10-minute window avoids alerting during cold starts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pods:&lt;/strong&gt; Crash-looping (more than 3 restarts in 15 minutes), memory above 85% of limit. Crash loops are critical because they mean the service is fundamentally broken. Memory warnings give you time to increase limits before OOMKills start.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Debugging That Taught Me the Most
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The OIDC mismatch.&lt;/strong&gt; I reused the EKS Terraform from Project 3, but the EBS CSI driver's IAM role still had the old cluster's OIDC provider URL. Every &lt;code&gt;AssumeRoleWithWebIdentity&lt;/code&gt; call failed with AccessDenied, but the error doesn't say "wrong OIDC provider", it just says "not authorized." I had to compare the role's trust policy against the current cluster's OIDC issuer to find the mismatch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The empty service account annotation.&lt;/strong&gt; After reinstalling the AWS Load Balancer Controller with Helm, the service account had &lt;code&gt;eks.amazonaws.io/role-arn: ""&lt;/code&gt;, an empty string instead of the actual ARN. The controller fell back to the node role, which didn't have ELB permissions. A &lt;code&gt;kubectl annotate --overwrite&lt;/code&gt; fixed it, but I only found it by checking the SA's YAML directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ServiceMonitor vs additionalScrapeConfigs.&lt;/strong&gt; I spent time trying to make &lt;code&gt;additionalScrapeConfigs&lt;/code&gt; work before learning that the Prometheus operator intentionally manages config through CRDs. &lt;code&gt;ServiceMonitor&lt;/code&gt; is the right abstraction, it's declarative, it uses label selectors, and the operator reconciles it automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters for Interviews
&lt;/h2&gt;

&lt;p&gt;Before this project, my monitoring answer was "we used Grafana and Prometheus." Now I can explain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to instrument an application with counters (requests), histograms (latency), and gauges (connections)&lt;/li&gt;
&lt;li&gt;Why &lt;code&gt;rate()&lt;/code&gt; over &lt;code&gt;increase()&lt;/code&gt; for dashboards, and why &lt;code&gt;histogram_quantile()&lt;/code&gt; for latency percentiles&lt;/li&gt;
&lt;li&gt;How ServiceMonitors work with the Prometheus operator for service discovery&lt;/li&gt;
&lt;li&gt;What alerts are worth setting up and why each threshold was chosen&lt;/li&gt;
&lt;li&gt;How to deploy and manage a monitoring stack via GitOps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When an interviewer asks "how would you know if your service is having issues?", I have a 9-panel dashboard and 9 alert rules to point to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub repo&lt;/strong&gt;: &lt;a href="https://github.com/augusthottie/gitops-eks" rel="noopener noreferrer"&gt;gitops-eks&lt;/a&gt; (includes observability stack)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project 3 (GitOps on EKS)&lt;/strong&gt;: &lt;a href="https://dev.to/augusthottie/i-set-up-gitops-on-eks-with-argocd-heres-what-kubernetes-actually-looks-like-in-production-1961"&gt;Blog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project 2 (3-Tier Architecture)&lt;/strong&gt;: &lt;a href="https://dev.to/augusthottie/i-built-a-3-tier-aws-architecture-with-terraform-modules-ecs-fargate-rds-and-elasticache-pj3"&gt;Blog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project 1 (CI/CD Pipeline)&lt;/strong&gt;: &lt;a href="https://dev.to/augusthottie/i-built-a-full-aws-cicd-pipeline-with-bluegreen-deployments-heres-everything-i-learned-5899"&gt;Blog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio&lt;/strong&gt;: &lt;a href="https://www.augusthottie.com/" rel="noopener noreferrer"&gt;augusthottie.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Building my DevOps portfolio ahead of the AWS DevOps Professional certification. Connect on &lt;a href="https://www.linkedin.com/in/jessica-chioma-chimex/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, I'd love to hear what you're building.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>grafana</category>
      <category>prometheus</category>
      <category>kubernetes</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>I Set Up GitOps on EKS with ArgoCD, Here's What Kubernetes Actually Looks Like in Production</title>
      <dc:creator>augusthottie</dc:creator>
      <pubDate>Tue, 17 Mar 2026 13:18:56 +0000</pubDate>
      <link>https://forem.com/augusthottie/i-set-up-gitops-on-eks-with-argocd-heres-what-kubernetes-actually-looks-like-in-production-1961</link>
      <guid>https://forem.com/augusthottie/i-set-up-gitops-on-eks-with-argocd-heres-what-kubernetes-actually-looks-like-in-production-1961</guid>
      <description>&lt;p&gt;I had a Kubernetes problem. I could talk about it, reference it on my resume, and deploy to existing clusters. But I'd never provisioned one from scratch, written a Helm chart, or set up GitOps. That gap showed, and I knew interviewers could smell it.&lt;/p&gt;

&lt;p&gt;So I built the whole thing: EKS cluster with Terraform, custom Helm chart, ArgoCD for GitOps, and a real application that talks to PostgreSQL and Redis. Push to &lt;code&gt;main&lt;/code&gt;, ArgoCD syncs, pods roll out. No &lt;code&gt;kubectl apply&lt;/code&gt; in sight. This is what I learned.&lt;/p&gt;

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

&lt;p&gt;A GitOps pipeline where Git is the only way deployments happen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git push → GitHub → ArgoCD (polls every 3m) → Helm sync → EKS cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application is a Notes API running on Bun/Express with PostgreSQL for persistence and Redis for caching, both running as pods inside the cluster. Two API replicas sit behind an ALB provisioned automatically by the AWS Load Balancer Controller from a Kubernetes Ingress resource.&lt;/p&gt;

&lt;p&gt;Every response includes a &lt;code&gt;pod&lt;/code&gt; field showing which replica served the request. Hit the endpoint twice and you'll see different pod names, load balancing in action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provisioning EKS with Terraform
&lt;/h2&gt;

&lt;p&gt;I wrote two Terraform modules: one for the VPC and one for the EKS cluster.&lt;/p&gt;

&lt;p&gt;The VPC module creates public and private subnets across two AZs, with a NAT Gateway for outbound traffic from private subnets. The critical detail for EKS: every subnet needs specific tags so Kubernetes knows which subnets to use for load balancers.&lt;/p&gt;

&lt;p&gt;Public subnets get &lt;code&gt;kubernetes.io/role/elb = 1&lt;/code&gt; (for internet-facing ALBs). Private subnets get &lt;code&gt;kubernetes.io/role/internal-elb = 1&lt;/code&gt;. Miss these tags and the AWS Load Balancer Controller silently fails to create ALBs.&lt;/p&gt;

&lt;p&gt;The EKS module creates the cluster, a managed node group (2x t3.medium), an OIDC provider for IRSA, and IAM roles for both the nodes and the LB controller.&lt;/p&gt;

&lt;p&gt;27 resources. &lt;code&gt;terraform apply&lt;/code&gt; takes about 15-20 minutes because EKS clusters are slow to provision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing the Helm Chart
&lt;/h2&gt;

&lt;p&gt;This was the part I had the least experience with. The chart has 8 templates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Namespace&lt;/strong&gt;: isolates everything in &lt;code&gt;three-tier&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ConfigMap&lt;/strong&gt;: database host, Redis host, app version&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret&lt;/strong&gt;: database password&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment&lt;/strong&gt;: API with 2 replicas, health checks, resource limits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service&lt;/strong&gt;: ClusterIP exposing port 80 internally&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ingress&lt;/strong&gt;: annotated for the AWS Load Balancer Controller to create an ALB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL StatefulSet&lt;/strong&gt;: with a PersistentVolumeClaim for data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis Deployment&lt;/strong&gt;: ephemeral cache with LRU eviction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything configurable lives in &lt;code&gt;values.yaml&lt;/code&gt;. Want 3 replicas? Change one number. Different image tag? One line. That's the power of Helm!&lt;/p&gt;

&lt;h2&gt;
  
  
  ArgoCD: The GitOps Engine
&lt;/h2&gt;

&lt;p&gt;ArgoCD watches a Git repo and makes the cluster match what's in Git. The application definition is simple:&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;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/augusthottie/gitops-eks&lt;/span&gt;
    &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helm/three-tier-app&lt;/span&gt;

  &lt;span class="na"&gt;syncPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;automated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;prune&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;      &lt;span class="c1"&gt;# Delete resources removed from Git&lt;/span&gt;
      &lt;span class="na"&gt;selfHeal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;   &lt;span class="c1"&gt;# Revert manual kubectl changes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;prune: true&lt;/code&gt; means if you delete a template from the Helm chart and push, ArgoCD deletes the corresponding resource from the cluster. &lt;code&gt;selfHeal: true&lt;/code&gt; means if someone runs &lt;code&gt;kubectl edit&lt;/code&gt; to change something manually, ArgoCD reverts it back to what Git says. Git is the source of truth, always!&lt;/p&gt;

&lt;p&gt;The ArgoCD UI is beautiful. You get a tree view of every resource, their health status, sync status, and the Git commit that triggered each sync.&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%2Fq721t9dt6labogrrhg18.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq721t9dt6labogrrhg18.png" alt="HELM CHART"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problems I Hit (All of Them)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  EBS CSI Driver
&lt;/h3&gt;

&lt;p&gt;My PostgreSQL StatefulSet stayed in &lt;code&gt;Pending&lt;/code&gt; forever. The PersistentVolumeClaim couldn't bind because EKS doesn't include the EBS CSI driver by default, it's an addon you install separately with its own IAM role.&lt;/p&gt;

&lt;p&gt;The fix: install the &lt;code&gt;aws-ebs-csi-driver&lt;/code&gt; addon via &lt;code&gt;aws eks create-addon&lt;/code&gt;. But even that failed initially because &lt;code&gt;eksctl create iamserviceaccount&lt;/code&gt; created a service account that conflicted with the addon. I had to delete and recreate with &lt;code&gt;--resolve-conflicts OVERWRITE&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  IRSA Not Working for LB Controller
&lt;/h3&gt;

&lt;p&gt;The AWS Load Balancer Controller kept failing with "AccessDenied" errors, but the IAM policy was correct. The problem: it was using the node role instead of the IRSA role. The service account had the right annotation, but the pods were created before the annotation was applied.&lt;/p&gt;

&lt;p&gt;Fix: &lt;code&gt;kubectl rollout restart deployment/aws-load-balancer-controller&lt;/code&gt;. The new pods picked up the service account annotation and used the correct IRSA role.&lt;/p&gt;

&lt;h3&gt;
  
  
  ArgoCD CRD Too Large
&lt;/h3&gt;

&lt;p&gt;Installing ArgoCD with &lt;code&gt;kubectl apply&lt;/code&gt; failed because the &lt;code&gt;applicationsets&lt;/code&gt; CRD exceeded the annotation size limit for client-side apply. The fix: &lt;code&gt;--server-side=true --force-conflicts&lt;/code&gt;. This is a known ArgoCD issue that everyone hits.&lt;/p&gt;

&lt;h3&gt;
  
  
  ConfigMap Changes Don't Restart Pods
&lt;/h3&gt;

&lt;p&gt;I changed the app version in &lt;code&gt;values.yaml&lt;/code&gt;, pushed to GitHub, ArgoCD synced the ConfigMap, but the pods kept serving the old version. Kubernetes doesn't restart pods when a ConfigMap changes, you need a checksum annotation on the pod template that changes when the ConfigMap changes, forcing a rollout.&lt;/p&gt;

&lt;h3&gt;
  
  
  exec format error (Again)
&lt;/h3&gt;

&lt;p&gt;Same issue from Project 2 — built the Docker image on my Mac (ARM), Kubernetes nodes are x86_64. &lt;code&gt;--platform linux/amd64&lt;/code&gt; on every build. I'll never forget this one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the GitOps Flow
&lt;/h2&gt;

&lt;p&gt;The demo that proves everything works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Change &lt;code&gt;app.version&lt;/code&gt; in &lt;code&gt;values.yaml&lt;/code&gt; from &lt;code&gt;1.0.0&lt;/code&gt; to &lt;code&gt;1.1.0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git push origin main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Wait for ArgoCD to sync (up to 3 minutes)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;curl /info&lt;/code&gt; returns &lt;code&gt;"version": "1.1.0"&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No CI/CD pipeline. No deployment commands. No SSH. Git push is the deployment.&lt;/p&gt;

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

&lt;p&gt;Before this project, my Kubernetes answer in interviews was... vague. I'd deployed to existing clusters, used Helm to install other people's charts, and read a lot of docs. But I hadn't actually provisioned a cluster, written my own Helm chart, or set up GitOps from scratch.&lt;/p&gt;

&lt;p&gt;Now I can talk about EKS provisioning, OIDC providers, IRSA, and why your node group needs specific IAM policies. I can explain why StatefulSets exist (PostgreSQL needs stable storage) and why Deployments don't care (Redis can lose its data). I know that the EBS CSI driver isn't installed by default and that it will silently break your PVCs if you forget it.&lt;/p&gt;

&lt;p&gt;Every problem I hit, the exec format error, the IRSA annotation not being picked up, ArgoCD CRDs being too large for kubectl, these are real production issues. Not textbook scenarios. The kind of stuff that comes up in interviews when someone asks "tell me about a time you debugged something in Kubernetes."&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Warning
&lt;/h2&gt;

&lt;p&gt;EKS is expensive for learning: ~$181/month (control plane $73, nodes $60, NAT $32, ALB $16). Always &lt;code&gt;terraform destroy&lt;/code&gt; when you're not working. You can bring it back in 20 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub repo&lt;/strong&gt;: &lt;a href="https://github.com/augusthottie/gitops-eks" rel="noopener noreferrer"&gt;gitops-eks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project 1 (CI/CD Pipeline)&lt;/strong&gt;: &lt;a href="https://dev.to/augusthottie/i-built-a-full-aws-cicd-pipeline-with-bluegreen-deployments-heres-everything-i-learned-5899"&gt;Part 1&lt;/a&gt; | &lt;a href="https://dev.to/augusthottie/i-broke-my-aws-pipeline-on-purpose-and-codified-everything-in-terraform-n7k"&gt;Part 2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project 2 (3-Tier Architecture)&lt;/strong&gt;: &lt;a href="https://dev.to/augusthottie/i-built-a-3-tier-aws-architecture-with-terraform-modules-ecs-fargate-rds-and-elasticache-pj3"&gt;Blog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio&lt;/strong&gt;: &lt;a href="https://www.augusthottie.com/" rel="noopener noreferrer"&gt;augusthottie.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Building my DevOps portfolio ahead of the AWS DevOps Professional certification. Connect on &lt;a href="https://www.linkedin.com/in/jessica-chioma-chimex/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, I'd love to hear what you're building.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>aws</category>
      <category>terraform</category>
    </item>
    <item>
      <title>I Built a 3-Tier AWS Architecture with Terraform Modules, ECS Fargate, RDS, and ElastiCache</title>
      <dc:creator>augusthottie</dc:creator>
      <pubDate>Tue, 10 Mar 2026 15:08:54 +0000</pubDate>
      <link>https://forem.com/augusthottie/i-built-a-3-tier-aws-architecture-with-terraform-modules-ecs-fargate-rds-and-elasticache-pj3</link>
      <guid>https://forem.com/augusthottie/i-built-a-3-tier-aws-architecture-with-terraform-modules-ecs-fargate-rds-and-elasticache-pj3</guid>
      <description>&lt;p&gt;My last project was a CI/CD pipeline with blue/green deployments. It taught me CodeDeploy, CodePipeline, and a lot about IAM. But it ran on EC2 instances in a default VPC, no custom networking, no containers, no database tier.&lt;/p&gt;

&lt;p&gt;This time I wanted to build what companies actually run in production: a 3-tier architecture with proper network isolation, serverless containers, a managed database, and an in-memory cache. All codified in Terraform modules.&lt;/p&gt;

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

&lt;p&gt;A Node.js API running on ECS Fargate that talks to PostgreSQL (RDS) and Redis (ElastiCache), deployed inside a custom VPC with public and private subnets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet → ALB (public subnets)
              ↓ :3000
         ECS Fargate (private subnets)
         Bun + Express API
              ↓ :5432          ↓ :6379
         RDS PostgreSQL    ElastiCache Redis
         (private subnets) (private subnets)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ALB is the only thing exposed to the internet. ECS, RDS, and Redis all sit in private subnets with no public IP addresses. Each tier's security group only allows traffic from the tier above it. The entire infrastructure is defined in 6 Terraform modules — 37 resources created with one command.&lt;/p&gt;

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

&lt;p&gt;If you're interviewing for DevOps or cloud engineering roles, "I deployed an app to EC2" doesn't differentiate you. Interviewers want to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can you design a VPC from scratch with proper subnet segmentation?&lt;/li&gt;
&lt;li&gt;Do you understand why databases belong in private subnets?&lt;/li&gt;
&lt;li&gt;Can you explain the difference between an Internet Gateway and a NAT Gateway?&lt;/li&gt;
&lt;li&gt;Have you actually worked with ECS Fargate, not just read about it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This project answers all of those with working code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Network Layer
&lt;/h2&gt;

&lt;p&gt;This was the foundation everything else depended on. I created a VPC with &lt;code&gt;10.0.0.0/16&lt;/code&gt; split across two availability zones:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Subnet&lt;/th&gt;
&lt;th&gt;CIDR&lt;/th&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Internet Access&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Public 1a&lt;/td&gt;
&lt;td&gt;10.0.0.0/20&lt;/td&gt;
&lt;td&gt;ALB, NAT Gateway&lt;/td&gt;
&lt;td&gt;Direct via IGW&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public 1b&lt;/td&gt;
&lt;td&gt;10.0.16.0/20&lt;/td&gt;
&lt;td&gt;ALB (multi-AZ)&lt;/td&gt;
&lt;td&gt;Direct via IGW&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Private 1a&lt;/td&gt;
&lt;td&gt;10.0.32.0/20&lt;/td&gt;
&lt;td&gt;ECS, RDS&lt;/td&gt;
&lt;td&gt;Outbound only via NAT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Private 1b&lt;/td&gt;
&lt;td&gt;10.0.48.0/20&lt;/td&gt;
&lt;td&gt;ECS, ElastiCache&lt;/td&gt;
&lt;td&gt;Outbound only via NAT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;p&gt;The key design decision: &lt;strong&gt;everything except the ALB goes in private subnets.&lt;/strong&gt; The ECS tasks need outbound internet access (to pull images from ECR), so they route through a NAT Gateway in the public subnet. But nothing on the internet can reach them directly.&lt;/p&gt;

&lt;p&gt;Each Terraform module is self-contained. The VPC module outputs subnet IDs and the VPC ID. Other modules consume those outputs without knowing anything about how the network is built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Group Boundaries
&lt;/h2&gt;

&lt;p&gt;This is the part that makes this a real 3-tier architecture, not just "three things in the same VPC." Each tier has its own security group, and the rules enforce strict boundaries:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Security Group&lt;/th&gt;
&lt;th&gt;Allows Inbound&lt;/th&gt;
&lt;th&gt;From&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;alb-sg&lt;/td&gt;
&lt;td&gt;TCP 80&lt;/td&gt;
&lt;td&gt;0.0.0.0/0 (the internet)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ecs-sg&lt;/td&gt;
&lt;td&gt;TCP 3000&lt;/td&gt;
&lt;td&gt;alb-sg only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rds-sg&lt;/td&gt;
&lt;td&gt;TCP 5432&lt;/td&gt;
&lt;td&gt;ecs-sg only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redis-sg&lt;/td&gt;
&lt;td&gt;TCP 6379&lt;/td&gt;
&lt;td&gt;ecs-sg only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No security group references a CIDR block except the ALB. Everything else references another security group. This means if an ECS task gets compromised, it can only reach the database and cache, not the internet, not other subnets, not other services.&lt;/p&gt;

&lt;p&gt;This is how production environments are designed, and explaining it in an interview immediately signals you understand network security beyond "I opened port 22."&lt;/p&gt;

&lt;h2&gt;
  
  
  ECS Fargate(Containers Without Servers)
&lt;/h2&gt;

&lt;p&gt;I used Fargate instead of EC2 for the compute layer. No instances to patch, no AMIs to maintain, no Auto Scaling Groups to configure. You define a task (CPU, memory, container image, environment variables) and Fargate runs it.&lt;/p&gt;

&lt;p&gt;The task definition connects the app to both RDS and Redis through environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;DB_HOST&lt;/span&gt;     → &lt;span class="n"&gt;RDS&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt; (&lt;span class="n"&gt;injected&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="n"&gt;Terraform&lt;/span&gt;)
&lt;span class="n"&gt;DB_PASSWORD&lt;/span&gt; → &lt;span class="n"&gt;Secrets&lt;/span&gt; &lt;span class="n"&gt;Manager&lt;/span&gt; &lt;span class="n"&gt;ARN&lt;/span&gt; (&lt;span class="n"&gt;resolved&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="n"&gt;launch&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="n"&gt;ECS&lt;/span&gt;)
&lt;span class="n"&gt;REDIS_HOST&lt;/span&gt;  → &lt;span class="n"&gt;ElastiCache&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt; (&lt;span class="n"&gt;injected&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="n"&gt;Terraform&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database password never touches Terraform state as plaintext and never appears in environment variable logs. ECS resolves it from Secrets Manager at runtime using the task execution role's IAM permissions.&lt;/p&gt;

&lt;p&gt;One thing I enabled that's worth mentioning: the &lt;strong&gt;deployment circuit breaker&lt;/strong&gt; with rollback. If a new task definition fails to start (bad image, crash loop, health check failure), ECS automatically stops the deployment and rolls back to the last working version. Same concept as the CodeDeploy auto-rollback from my first project, but built into ECS.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Application(Proving the Architecture Works)
&lt;/h2&gt;

&lt;p&gt;I built a fresh Express API specifically designed to demonstrate all three tiers working together. The key endpoint is &lt;code&gt;GET /items&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;First request: queries PostgreSQL, caches the result in Redis for 30 seconds, returns &lt;code&gt;"source": "database"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Second request (within 30s): returns the cached data from Redis, &lt;code&gt;"source": "cache"&lt;/code&gt;, with 1ms latency.&lt;/p&gt;

&lt;p&gt;Any write operation (POST, PUT, DELETE) invalidates the Redis cache so the next read gets fresh data from PostgreSQL. This is a standard cache-aside pattern used in production systems.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/health&lt;/code&gt; endpoint checks both database and cache connectivity. If either is down, it returns a 503, which the ALB detects and stops routing traffic to that task.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"healthy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"services"&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;"database"&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;"connected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-09T14:50:37.136Z"&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;"cache"&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;"connected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"latency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="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;h2&gt;
  
  
  Terraform Modules(Reusable Infrastructure)
&lt;/h2&gt;

&lt;p&gt;Instead of one giant Terraform file, I split everything into 6 modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;modules/
├── vpc/             # Network foundation
├── security-groups/ # Tier boundaries
├── alb/             # Load balancing
├── ecs/             # Container orchestration
├── rds/             # Database
└── elasticache/     # Caching
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each module has its own &lt;code&gt;variables.tf&lt;/code&gt;, &lt;code&gt;main.tf&lt;/code&gt;, and &lt;code&gt;outputs.tf&lt;/code&gt;. The root &lt;code&gt;main.tf&lt;/code&gt; wires them together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"ecs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/ecs"&lt;/span&gt;
  &lt;span class="nx"&gt;private_subnet_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_subnet_ids&lt;/span&gt;
  &lt;span class="nx"&gt;security_group_id&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;security_groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs_sg_id&lt;/span&gt;
  &lt;span class="nx"&gt;target_group_arn&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target_group_arn&lt;/span&gt;
  &lt;span class="nx"&gt;db_host&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;
  &lt;span class="nx"&gt;redis_host&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elasticache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;
  &lt;span class="nx"&gt;db_secret_arn&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secret_arn&lt;/span&gt;
  &lt;span class="nx"&gt;container_image&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${aws_ecr_repository.app.repository_url}:latest"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The advantage of modules: you can reuse the VPC module for a completely different project, or create dev/staging/prod environments by calling the same modules with different variables. That's the next iteration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problems I Hit
&lt;/h2&gt;

&lt;h3&gt;
  
  
  exec format error
&lt;/h3&gt;

&lt;p&gt;I built the Docker image on my Mac (Apple Silicon = ARM) and pushed it to ECR. Fargate runs x86_64. The container started and immediately crashed with &lt;code&gt;exec format error&lt;/code&gt;, no other context.&lt;/p&gt;

&lt;p&gt;The fix: &lt;code&gt;docker build --platform linux/amd64&lt;/code&gt;. Always specify the platform when building for Fargate.&lt;/p&gt;

&lt;h3&gt;
  
  
  no pg_hba.conf entry
&lt;/h3&gt;

&lt;p&gt;RDS PostgreSQL requires SSL by default. My app was connecting without it. The error message is a PostgreSQL internals reference that doesn't mention SSL at all.&lt;/p&gt;

&lt;p&gt;The fix: add &lt;code&gt;ssl: { rejectUnauthorized: false }&lt;/code&gt; to the connection pool config.&lt;/p&gt;

&lt;h3&gt;
  
  
  CannotPullContainerError
&lt;/h3&gt;

&lt;p&gt;I deployed the ECS service before pushing the Docker image to ECR. Fargate couldn't find the image, retried 7 times, and tripped the circuit breaker. After pushing the correct image, new deployments still failed because the breaker was already tripped.&lt;/p&gt;

&lt;p&gt;The fix: &lt;code&gt;aws ecs update-service --force-new-deployment&lt;/code&gt; resets the circuit breaker and triggers a fresh deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Target type: ip vs instance
&lt;/h3&gt;

&lt;p&gt;Fargate requires &lt;code&gt;target_type = "ip"&lt;/code&gt; on the ALB target group. EC2-based services use &lt;code&gt;"instance"&lt;/code&gt;. Using the wrong one causes silent registration failures where ECS reports the task as running but the ALB never sees it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Breakdown
&lt;/h2&gt;

&lt;p&gt;For anyone worried about the AWS bill:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NAT Gateway&lt;/td&gt;
&lt;td&gt;~$32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ALB&lt;/td&gt;
&lt;td&gt;~$16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ElastiCache&lt;/td&gt;
&lt;td&gt;~$12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECS Fargate&lt;/td&gt;
&lt;td&gt;~$9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RDS db.t3.micro&lt;/td&gt;
&lt;td&gt;Free tier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECR + Secrets Manager&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$70/month&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The NAT Gateway is the biggest surprise, it's more expensive than the ALB. In production you'd need it, but for learning, &lt;code&gt;terraform destroy&lt;/code&gt; when you're not working saves real money.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Add HTTPS from the start.&lt;/strong&gt; ACM + Route53 would make this production-ready. HTTP-only is fine for a demo but wouldn't pass a security review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Terraform workspaces for multi-environment.&lt;/strong&gt; Right now it's a single environment. The module structure supports dev/staging/prod, just pass different variables. That's the next iteration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto Scaling for ECS.&lt;/strong&gt; One task is fine for a demo, but production needs scaling policies based on CPU and request count.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI/CD integration.&lt;/strong&gt; This project deploys manually with &lt;code&gt;docker push&lt;/code&gt; and &lt;code&gt;ecs update-service&lt;/code&gt;. Connecting it to CodePipeline (from Project 1) would complete the picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Proves on a Resume
&lt;/h2&gt;

&lt;p&gt;This project covers territory that most junior/mid-level candidates don't demonstrate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Custom VPC design&lt;/strong&gt;: with proper public/private subnet segmentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECS Fargate&lt;/strong&gt;: serverless containers, not just EC2&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-tier security&lt;/strong&gt;: security groups referencing other security groups, not CIDRs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed data services&lt;/strong&gt;: RDS + ElastiCache with proper secret handling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terraform modules&lt;/strong&gt;: reusable, composable infrastructure, not flat files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real debugging&lt;/strong&gt;: ARM vs x86, SSL requirements, circuit breakers, NAT Gateway necessity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If an interviewer asks "tell me about a complex AWS architecture you've built," this project gives you 20 minutes of material.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub repo&lt;/strong&gt;: &lt;a href="https://github.com/augusthottie/aws-3-tier-project" rel="noopener noreferrer"&gt;three-tier-aws&lt;/a&gt; (Terraform + application code)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Pipeline (CI/CD Pipeline)&lt;/strong&gt;: &lt;a href="https://dev.to/augusthottie/i-built-a-full-aws-cicd-pipeline-with-bluegreen-deployments-heres-everything-i-learned-5899"&gt;Part 1&lt;/a&gt; | &lt;a href="https://dev.to/augusthottie/i-broke-my-aws-pipeline-on-purpose-and-codified-everything-in-terraform-n7k"&gt;Part 2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio&lt;/strong&gt;: &lt;a href="https://www.augusthottie.com/" rel="noopener noreferrer"&gt;augusthottie.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Building my DevOps portfolio ahead of the AWS DevOps Professional certification. Connect with me on &lt;a href="https://www.linkedin.com/in/jessica-chioma-chimex/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, I'd love to hear what you're building.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>terraform</category>
      <category>ecs</category>
    </item>
    <item>
      <title>I Broke My AWS Pipeline on Purpose and Codified Everything in Terraform</title>
      <dc:creator>augusthottie</dc:creator>
      <pubDate>Thu, 05 Mar 2026 16:36:54 +0000</pubDate>
      <link>https://forem.com/augusthottie/i-broke-my-aws-pipeline-on-purpose-and-codified-everything-in-terraform-n7k</link>
      <guid>https://forem.com/augusthottie/i-broke-my-aws-pipeline-on-purpose-and-codified-everything-in-terraform-n7k</guid>
      <description>&lt;p&gt;Testing automatic rollback by deploying broken code, then codifying 30 AWS resources into Terraform so the entire CI/CD pipeline can be created with one command.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is Part 2. &lt;a href="https://dev.to/augusthottie/i-built-a-full-aws-cicd-pipeline-with-bluegreen-deployments-heres-everything-i-learned-5899"&gt;Read Part 1 here: I Built a Full AWS CI/CD Pipeline with Blue/Green Deployments&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Last week I built an AWS-native CI/CD pipeline with blue/green deployments. Someone on LinkedIn asked a great question: "Did you test that rollback works without manual intervention?"&lt;/p&gt;

&lt;p&gt;I hadn't. So I did. Then I codified the entire infrastructure in Terraform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proving Rollback Actually Works
&lt;/h2&gt;

&lt;p&gt;It's one thing to configure auto-rollback. It's another to watch it fire. I needed to deploy a broken version and confirm the system recovers on its own.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two Layers of Safety
&lt;/h3&gt;

&lt;p&gt;The pipeline has two layers of protection, and I accidentally discovered the first one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 1: Tests catch bad code in CodeBuild.&lt;/strong&gt; My first attempt was changing the &lt;code&gt;/health&lt;/code&gt; endpoint to return a 500. But &lt;code&gt;bun test&lt;/code&gt; tests that endpoint, so the build failed before the code ever reached deployment. The pipeline stopped at the Build stage. Approval and Deploy never ran.&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%2Fy0aol8wv6wnssjlo95nz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy0aol8wv6wnssjlo95nz.png" alt="Architecture diagram" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's actually a great result, the first safety net caught the problem before it could go anywhere. But it wasn't what I wanted to test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 2: CodeDeploy catches failures during deployment.&lt;/strong&gt; To test this, I needed code that passes tests but fails during deployment. I left the app healthy and instead broke the &lt;code&gt;validate_service.sh&lt;/code&gt; script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&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;"Simulating deployment validation failure"&lt;/span&gt;
&lt;span class="nb"&gt;exit &lt;/span&gt;1

&lt;span class="c"&gt;# ... rest of the script never executes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time the build passed, tests passed, I approved the deployment, and CodeDeploy deployed to the green environment. Then &lt;code&gt;ValidateService&lt;/code&gt; ran, hit the &lt;code&gt;exit 1&lt;/code&gt;, and the deployment failed.&lt;/p&gt;

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

&lt;p&gt;CodeDeploy detected the failure and triggered an automatic rollback:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trigger:&lt;/strong&gt; AutomatedRollback&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution type:&lt;/strong&gt; ROLLBACK&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status:&lt;/strong&gt; Succeeded&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%2Fg1pkyjxqfrvfrwx9cde5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg1pkyjxqfrvfrwx9cde5.png" alt="Architecture diagram" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No SSH. No console clicks. No human intervention. The green instances were terminated, traffic stayed on the original blue environment, and users were never affected.&lt;/p&gt;

&lt;p&gt;The rollback cycle took about 15 minutes, mostly ASG provisioning and the 5-minute termination wait. In production you'd tune those timers, but the mechanics are sound.&lt;/p&gt;

&lt;h3&gt;
  
  
  One More IAM Surprise
&lt;/h3&gt;

&lt;p&gt;The automated rollback initially failed because the CodePipeline service role was missing &lt;code&gt;codedeploy:GetApplicationRevision&lt;/code&gt;. AWS doesn't tell you this is needed until rollback actually fires.&lt;/p&gt;

&lt;p&gt;This is a theme with this project: &lt;strong&gt;you don't discover the permission you're missing until you trigger the specific action that needs it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure as Code with Terraform
&lt;/h2&gt;

&lt;p&gt;After proving everything works, I codified the entire infrastructure. The goal: anyone should be able to recreate this pipeline with &lt;code&gt;terraform apply&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform/
├── main.tf              # Provider, default VPC data sources
├── variables.tf         # Configurable inputs
├── outputs.tf           # ALB URL, resource names
├── iam.tf               # All IAM roles + policies
├── s3.tf                # Artifact bucket with lifecycle rules
├── sns.tf               # Approval notification topic
├── security-groups.tf   # ALB + EC2 security groups
├── alb.tf               # Load balancer, target group, listener
├── asg.tf               # Launch template + Auto Scaling Group
├── codebuild.tf         # Build project with S3 caching
├── codedeploy.tf        # App + blue/green deployment group
└── codepipeline.tf      # Pipeline + CloudWatch Event trigger
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;30 resources. One command. The full pipeline, IAM roles, S3 bucket, SNS topic, security groups, ALB, ASG with launch template, CodeBuild project, CodeDeploy blue/green deployment group, CodePipeline with all four stages, and the CloudWatch Event rule to auto-trigger on pushes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The IAM Battle (Round 2)
&lt;/h3&gt;

&lt;p&gt;I thought I'd learned all the IAM lessons during the manual build. Terraform taught me new ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blue/green needs broad Auto Scaling permissions.&lt;/strong&gt; I started with a carefully scoped list of 17 specific &lt;code&gt;autoscaling:&lt;/code&gt; actions. The deployment group wouldn't even create, it needed &lt;code&gt;autoscaling:RecordLifecycleActionHeartbeat&lt;/code&gt;, which isn't in any AWS documentation for CodeDeploy. Even after adding that, deployments failed with the vague error "does not give you permission to perform operations in AmazonAutoScaling." The fix: &lt;code&gt;autoscaling:*&lt;/code&gt;. AWS's blue/green implementation calls undocumented internal actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Same story with Elastic Load Balancing.&lt;/strong&gt; Specific ELB permissions let CodeDeploy create the deployment group but failed during actual traffic shifting, the replacement instances couldn't register in the target group. The fix: &lt;code&gt;elasticloadbalancing:*&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;iam:PassRole&lt;/code&gt; needs wide scope.&lt;/strong&gt; I initially scoped &lt;code&gt;PassRole&lt;/code&gt; to specific role ARNs. Blue/green deployments pass roles to service-linked roles with unpredictable ARN patterns. The practical approach is &lt;code&gt;Resource: "*"&lt;/code&gt; with a condition restricting which services can receive the role:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
  &lt;span class="nx"&gt;Action&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"iam:PassRole"&lt;/span&gt;
  &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt;
  &lt;span class="nx"&gt;Condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;StringLike&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"iam:PassedToService"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"ec2.amazonaws.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"autoscaling.amazonaws.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"codedeploy.amazonaws.com"&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;IAM propagation causes race conditions.&lt;/strong&gt; The pipeline triggered immediately after Terraform created it, before IAM policies had propagated. The Source stage failed with "Insufficient permissions" even though the policy was correct. Retrying a minute later worked fine. This is a Terraform-specific issue, manual builds don't hit it because there's a human-speed delay between creating roles and using them.&lt;/p&gt;

&lt;h3&gt;
  
  
  CodeDeploy Agent Gotchas on Amazon Linux 2023
&lt;/h3&gt;

&lt;p&gt;The launch template user data needs careful handling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Add a 30-second sleep&lt;/strong&gt; before installing packages. The instance needs time to initialize before package managers work reliably.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;/tmp&lt;/code&gt; for downloads&lt;/strong&gt;, not &lt;code&gt;/home/ec2-user&lt;/code&gt;, the home directory may not exist during early boot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log everything&lt;/strong&gt; with &lt;code&gt;exec &amp;gt; /var/log/user-data.log 2&amp;gt;&amp;amp;1&lt;/code&gt; so you can debug via SSM if the agent doesn't start.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without these, instances boot but the CodeDeploy agent silently fails to install, and deployments time out with "CodeDeploy agent was not able to receive the lifecycle event."&lt;/p&gt;

&lt;h3&gt;
  
  
  Orphaned ASGs, The Undocumented Gotcha
&lt;/h3&gt;

&lt;p&gt;When blue/green deployments fail mid-way, they leave behind orphaned Auto Scaling Groups with names like &lt;code&gt;CodeDeploy_my-dg_d-ABC123&lt;/code&gt;. These block subsequent deployments because instances from the orphaned ASG are still registered in the target group as unhealthy.&lt;/p&gt;

&lt;p&gt;The fix: check for and delete them before retrying.&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;# Find orphaned ASGs&lt;/span&gt;
aws autoscaling describe-auto-scaling-groups &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"AutoScalingGroups[?contains(AutoScalingGroupName, 'CodeDeploy')].AutoScalingGroupName"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; text

&lt;span class="c"&gt;# Delete them&lt;/span&gt;
aws autoscaling delete-auto-scaling-group &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--auto-scaling-group-name&lt;/span&gt; &lt;span class="s2"&gt;"NAME_HERE"&lt;/span&gt; &lt;span class="nt"&gt;--force-delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This cost me hours of debugging. I'm writing it down so it doesn't cost you the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cost Advantage of Terraform
&lt;/h2&gt;

&lt;p&gt;With everything in Terraform, the economics change:&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;# Done for the day? Tear it all down.&lt;/span&gt;
terraform destroy

&lt;span class="c"&gt;# Ready to work again? Bring it all back.&lt;/span&gt;
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ALB alone costs ~$16/month. When you're learning and not actively using the pipeline, that adds up. Terraform lets you pay only for the hours you're actually working.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently Next Time
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start with &lt;code&gt;autoscaling:*&lt;/code&gt; and &lt;code&gt;elasticloadbalancing:*&lt;/code&gt;&lt;/strong&gt;, then tighten permissions later using CloudTrail logs to see which actions are actually called. Fighting undocumented permissions one at a time wastes hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add CloudWatch alarms for post-deploy monitoring.&lt;/strong&gt; Right now rollback only triggers if lifecycle hooks fail during deployment. In production, you'd want alarms monitoring error rates and latency after traffic shifts, with automatic rollback if metrics spike.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implement canary deployments.&lt;/strong&gt; Instead of shifting 100% of traffic at once, shift 10% first, monitor for a few minutes, then complete the rollout. CodeDeploy supports this with custom deployment configurations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test your failure modes.&lt;/strong&gt; Don't assume rollback works, prove it! Deploy a broken version and watch it recover. That confidence is worth more than any passing test.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Terraform turns a project into a product.&lt;/strong&gt; A manually-built pipeline is a demo. A Terraform-codified pipeline is something a team can use. And &lt;code&gt;terraform destroy&lt;/code&gt; changes the economics of learning on AWS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start broad with IAM, tighten later.&lt;/strong&gt; For blue/green deployments specifically, AWS calls undocumented internal actions. Scoped permissions fail silently with vague errors. Get it working first, then use CloudTrail to scope down.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Document the gotchas nobody else does.&lt;/strong&gt; Orphaned ASGs, agent installation timing, IAM propagation race conditions, these are the problems you'll actually hit in production, and they're not in any tutorial.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub repo&lt;/strong&gt;: &lt;a href="https://github.com/augusthottie/aws-pipeline-project" rel="noopener noreferrer"&gt;aws-pipeline-demo&lt;/a&gt; (includes Terraform)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 1&lt;/strong&gt;: &lt;a href="https://dev.to/augusthottie/i-built-a-full-aws-cicd-pipeline-with-bluegreen-deployments-heres-everything-i-learned-5899"&gt;I Built a Full AWS CI/CD Pipeline with Blue/Green Deployments&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio&lt;/strong&gt;: &lt;a href="https://www.augusthottie.com/" rel="noopener noreferrer"&gt;augusthottie.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;I'm building my DevOps portfolio ahead of targeting the AWS DevOps Professional certification. If you're on a similar journey, I'd love to connect, drop a comment or find me on &lt;a href="https://www.linkedin.com/in/jessica-chioma-chimex/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>devops</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>I Built a Full AWS CI/CD Pipeline with Blue/Green Deployments, Here's Everything I Learned</title>
      <dc:creator>augusthottie</dc:creator>
      <pubDate>Tue, 03 Mar 2026 16:51:08 +0000</pubDate>
      <link>https://forem.com/augusthottie/i-built-a-full-aws-cicd-pipeline-with-bluegreen-deployments-heres-everything-i-learned-5899</link>
      <guid>https://forem.com/augusthottie/i-built-a-full-aws-cicd-pipeline-with-bluegreen-deployments-heres-everything-i-learned-5899</guid>
      <description>&lt;p&gt;A hands-on walkthrough of building an end-to-end AWS-native CI/CD pipeline using CodeCommit, CodeBuild, CodeDeploy, and CodePipeline with zero-downtime blue/green deployments.&lt;/p&gt;

&lt;p&gt;Most DevOps tutorials stop at "set up a GitHub Actions workflow." That's fine, but if you're preparing for the AWS DevOps Professional exam or interviewing for AWS-heavy roles, you need to know the AWS-native CI/CD stack inside and out.&lt;/p&gt;

&lt;p&gt;So I built a full pipeline from scratch! No GitHub Actions, no Jenkins, no third-party tools. Just AWS services talking to each other, ending with zero-downtime blue/green deployments.&lt;/p&gt;

&lt;p&gt;This post walks through the entire build, the problems I ran into, and what I'd do differently.&lt;/p&gt;

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

&lt;p&gt;A simple Node.js/Express API deployed through a fully automated pipeline:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CodeCommit → CodeBuild → Manual Approval → CodeDeploy (Blue/Green)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every &lt;code&gt;git push&lt;/code&gt; to &lt;code&gt;main&lt;/code&gt; triggers the pipeline. CodeBuild installs dependencies and runs tests using Bun, then waits for manual approval via an SNS email notification. Once approved, CodeDeploy spins up a fresh Auto Scaling Group, deploys the new version, validates health checks through an ALB, shifts traffic, and terminates the old instances.&lt;/p&gt;

&lt;p&gt;Zero downtime. Fully automated after approval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AWS-Native Instead of GitHub Actions?
&lt;/h2&gt;

&lt;p&gt;I already had several projects using GitHub Actions. That's great, but it only tells half the story. AWS has its own CI/CD ecosystem, CodeCommit, CodeBuild, CodeDeploy, CodePipeline, and companies running on AWS often use these services because they integrate tightly with IAM, VPCs, and other AWS infrastructure.&lt;/p&gt;

&lt;p&gt;Understanding both gives you range. And for the AWS DevOps Professional exam, these services are tested heavily.&lt;/p&gt;

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

&lt;p&gt;I kept the app intentionally simple, the pipeline is the project, not the app. It's an Express API with four endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /health&lt;/code&gt;: returns a 200 with status info (used by the ALB and CodeDeploy for validation)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /&lt;/code&gt;: welcome message with available endpoints&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /info&lt;/code&gt;: app version, uptime, memory usage&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /deploy-info&lt;/code&gt;: deployment metadata from CodeDeploy environment variables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;/health&lt;/code&gt; endpoint is the most important one. It's what the ALB target group uses for health checks, and it's what the &lt;code&gt;validate_service.sh&lt;/code&gt; lifecycle script hits after deployment to confirm everything is working. If it fails, CodeDeploy rolls back automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Pipeline
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CodeCommit
&lt;/h3&gt;

&lt;p&gt;Nothing fancy here, create a repo, push your code. I used &lt;code&gt;git-remote-codecommit&lt;/code&gt; for authentication instead of HTTPS Git credentials because it uses your existing AWS CLI credentials and doesn't require generating separate passwords.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;git-remote-codecommit
git remote add origin codecommit::us-east-1://aws-pipeline-demo
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CodeBuild with Bun
&lt;/h3&gt;

&lt;p&gt;CodeBuild uses a &lt;code&gt;buildspec.yml&lt;/code&gt; file, think of it like a GitHub Actions workflow but for AWS. Mine installs Bun, runs &lt;code&gt;bun install --frozen-lockfile&lt;/code&gt;, executes tests with &lt;code&gt;bun test&lt;/code&gt;, and packages the artifact to S3.&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;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.2&lt;/span&gt;

&lt;span class="na"&gt;phases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runtime-versions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;nodejs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;
    &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl -fsSL https://bun.sh/install | bash&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export BUN_INSTALL="$HOME/.bun"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export PATH="$BUN_INSTALL/bin:$PATH"&lt;/span&gt;

  &lt;span class="na"&gt;pre_build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export BUN_INSTALL="$HOME/.bun"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export PATH="$BUN_INSTALL/bin:$PATH"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bun install --frozen-lockfile&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bun test&lt;/span&gt;

  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export BUN_INSTALL="$HOME/.bun"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export PATH="$BUN_INSTALL/bin:$PATH"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Build started on $(date)"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export APP_VERSION=$(bun -e "console.log(require('./package.json').version)")-$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "App version $APP_VERSION"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing that caught me early: &lt;code&gt;bun install --frozen-lockfile&lt;/code&gt; requires a &lt;code&gt;bun.lockb&lt;/code&gt; file in the repo. If you forget to commit it, the build fails with no helpful error message. Run &lt;code&gt;bun install&lt;/code&gt; locally first and push the lockfile.&lt;/p&gt;

&lt;p&gt;I also enabled &lt;strong&gt;S3 caching&lt;/strong&gt; for &lt;code&gt;node_modules&lt;/code&gt; and the Bun binary directory. The first build downloads everything, but subsequent builds skip the install step entirely when dependencies haven't changed. The actual build commands run in about 7 seconds, the rest of the ~4 minute build time is CodeBuild provisioning its container, which is unavoidable with on-demand compute.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Approval Gate
&lt;/h3&gt;

&lt;p&gt;Between build and deploy, I added a manual approval stage with SNS notifications. When the build passes, CodePipeline sends an email asking you to approve or reject the deployment.&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%2Fp7g1x6yy1oh87wvjps46.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp7g1x6yy1oh87wvjps46.png" alt=" " width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

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

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

&lt;p&gt;This is common in production pipelines, you don't always want every passing build to go straight to production. The approval stage gives you a checkpoint to review changes, run additional validation, or coordinate deployment timing.&lt;/p&gt;

&lt;p&gt;One gotcha: the &lt;code&gt;sns:Publish&lt;/code&gt; permission needs to be on the &lt;strong&gt;CodePipeline service role&lt;/strong&gt;, not the CodeBuild role. The approval action is triggered by CodePipeline, not CodeBuild. I initially added it to the wrong role and spent time debugging why emails weren't sending.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blue/Green Deployments (The Core of the Project)
&lt;/h2&gt;

&lt;p&gt;This is the part I'm most proud of. In-place deployments are simpler, but blue/green is what production environments use when downtime isn't acceptable.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;p&gt;CodeDeploy uses an &lt;code&gt;appspec.yml&lt;/code&gt; file that defines where files go and which scripts to run at each lifecycle stage:&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;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ApplicationStop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scripts/stop_app.sh&lt;/span&gt;
  &lt;span class="na"&gt;BeforeInstall&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scripts/before_install.sh&lt;/span&gt;
  &lt;span class="na"&gt;AfterInstall&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scripts/after_install.sh&lt;/span&gt;
  &lt;span class="na"&gt;ApplicationStart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scripts/start_app.sh&lt;/span&gt;
  &lt;span class="na"&gt;ValidateService&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scripts/validate_service.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each script handles a specific step: stop the old app, clean up, install dependencies, start the new version, and validate it's healthy.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Deployment Flow
&lt;/h3&gt;

&lt;p&gt;When CodeDeploy runs a blue/green deployment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clones the ASG&lt;/strong&gt;: creates a brand new Auto Scaling Group with the same configuration as the original&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Launches fresh instances&lt;/strong&gt;: new EC2 instances spin up with the CodeDeploy agent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runs lifecycle hooks&lt;/strong&gt;: your scripts execute in order (stop → before_install → after_install → start → validate)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health check&lt;/strong&gt;: the ALB confirms the new instances return 200 on &lt;code&gt;/health&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shifts traffic&lt;/strong&gt;: the ALB moves all traffic from the old target group to the new one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminates the old environment&lt;/strong&gt;: after a 5-minute wait, the original instances are killed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Users never see downtime because traffic only shifts after the new instances are confirmed healthy.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Thing Nobody Tells You About Blue/Green
&lt;/h3&gt;

&lt;p&gt;I was confused when my first blue/green deployment created a second Auto Scaling Group. I thought, "Why is it making a new one? I already have one."&lt;/p&gt;

&lt;p&gt;Here's what nobody explains upfront: &lt;strong&gt;your original ASG is just a template.&lt;/strong&gt; CodeDeploy copies it on the first deployment and replaces it. On the second deployment, it copies the replacement and replaces that. Every deployment creates a new ASG and destroys the old one. Your original ASG only survives until the first successful deployment.&lt;/p&gt;

&lt;p&gt;Once I understood this, the whole model clicked.&lt;/p&gt;

&lt;h2&gt;
  
  
  The IAM Nightmare (And How to Survive It)
&lt;/h2&gt;

&lt;p&gt;IAM was the single biggest time sink in this project. Blue/green deployments require an unusually broad set of permissions because CodeDeploy needs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create and delete Auto Scaling Groups&lt;/li&gt;
&lt;li&gt;Launch and terminate EC2 instances&lt;/li&gt;
&lt;li&gt;Modify ALB target groups and listeners&lt;/li&gt;
&lt;li&gt;Pass IAM roles to new instances (&lt;code&gt;iam:PassRole&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Read artifacts from S3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is a separate IAM action, and missing any one of them produces a vague error message. Here's what my CodeDeploy service role ended up needing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full Auto Scaling permissions (create, update, delete ASGs, lifecycle hooks, scaling policies)&lt;/li&gt;
&lt;li&gt;EC2 permissions (describe, run, terminate instances, create tags)&lt;/li&gt;
&lt;li&gt;Elastic Load Balancing permissions (describe and modify target groups, register/deregister targets)&lt;/li&gt;
&lt;li&gt;S3 read access to the artifact bucket&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;iam:PassRole&lt;/code&gt; for EC2 and Auto Scaling services&lt;/li&gt;
&lt;li&gt;SNS publish for notifications&lt;/li&gt;
&lt;li&gt;CloudWatch for alarms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My advice: start with the AWS managed &lt;code&gt;AWSCodeDeployRole&lt;/code&gt; policy and add custom permissions for blue/green. Don't try to build the policy from scratch, you'll miss something and spend hours debugging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Optimization
&lt;/h2&gt;

&lt;p&gt;A few things I did to keep builds fast and costs low:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;S3 caching&lt;/strong&gt;: CodeBuild caches &lt;code&gt;node_modules&lt;/code&gt; and the Bun binary between builds. When dependencies haven't changed, &lt;code&gt;bun install&lt;/code&gt; completes almost instantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bun over npm&lt;/strong&gt;: Bun's install and test execution is noticeably faster than npm/Jest. The entire install + test cycle takes about 7 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I tried using a custom Docker image&lt;/strong&gt; (&lt;code&gt;oven/bun:1&lt;/code&gt;) to skip installing Bun on every build. It worked locally, but Docker Hub's unauthenticated pull rate limit blocked it in CodeBuild. The fix would be pushing the image to Amazon ECR, but for this project the S3 cache approach was simpler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Breakdown
&lt;/h2&gt;

&lt;p&gt;For anyone worried about AWS bills:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CodePipeline&lt;/td&gt;
&lt;td&gt;1 free pipeline/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CodeBuild&lt;/td&gt;
&lt;td&gt;100 free build minutes/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EC2 t3.micro&lt;/td&gt;
&lt;td&gt;Free tier eligible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ALB&lt;/td&gt;
&lt;td&gt;~$16/month (the main cost)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S3 + SNS&lt;/td&gt;
&lt;td&gt;Negligible&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ALB is the biggest expense. Tear it down when you're not actively working on the project, and spin it back up when needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start with Terraform from day one.&lt;/strong&gt; I set everything up manually in the console first to learn how each service works, which was valuable for understanding. But recreating the setup would mean clicking through dozens of console screens. If I were doing this again, I'd codify everything in Terraform as I go.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use ECR for custom images.&lt;/strong&gt; Instead of Docker Hub, I'd push the Bun image to ECR to avoid rate limits and get faster pulls within AWS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add CloudWatch alarms for auto-rollback.&lt;/strong&gt; Right now, rollback only triggers if the health check fails during deployment. In production, you'd want CloudWatch alarms monitoring error rates and latency post-deployment, with automatic rollback if metrics spike.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;If you're learning DevOps or preparing for AWS certifications, building this pipeline taught me more than any course or practice exam:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;IAM is the real skill.&lt;/strong&gt; Anyone can configure a pipeline in the console. Understanding which roles need which permissions, and why, is what separates junior from mid-level DevOps engineers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Blue/green isn't magic.&lt;/strong&gt; It's just two environments, a load balancer, and a traffic switch. Once you understand the ASG cloning model, it's straightforward.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS-native CI/CD has tradeoffs.&lt;/strong&gt; It integrates beautifully with IAM, VPCs, and other AWS services. But it's more complex to set up than GitHub Actions, and CodeBuild's container provisioning adds latency. Choose based on your environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The debugging is the learning.&lt;/strong&gt; Every failed deployment, every permission error, every misconfigured health check taught me something I wouldn't have learned from documentation alone.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub/CodeCommit repo&lt;/strong&gt;: &lt;a href="https://github.com/augusthottie/aws-pipeline-project" rel="noopener noreferrer"&gt;aws-pipeline-demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio&lt;/strong&gt;: &lt;a href="https://www.augusthottie.com/" rel="noopener noreferrer"&gt;augusthottie.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Certifications&lt;/strong&gt;: &lt;a href="https://www.credly.com/badges/0f5c3227-ce72-482c-b360-f903a84248d2/public_url" rel="noopener noreferrer"&gt;AWS Cloud Practitioner&lt;/a&gt; | &lt;a href="https://www.credly.com/badges/c2b8eb5b-1f71-4c62-8bcf-0e0214c9c478/public_url" rel="noopener noreferrer"&gt;AWS Solutions Architect Associate&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;I'm currently building my DevOps portfolio ahead of targeting the AWS DevOps Professional certification. If you're on a similar journey, I'd love to connect, drop a comment or find me on &lt;a href="https://www.linkedin.com/in/jessica-chioma-chimex/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cicd</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Building a Command-Line Calculator in Zig - Tutorial</title>
      <dc:creator>augusthottie</dc:creator>
      <pubDate>Thu, 19 Dec 2024 17:28:44 +0000</pubDate>
      <link>https://forem.com/augusthottie/building-a-command-line-calculator-in-zig-tutorial-k26</link>
      <guid>https://forem.com/augusthottie/building-a-command-line-calculator-in-zig-tutorial-k26</guid>
      <description>&lt;p&gt;This tutorial will guide you through creating a simple command-line calculator using Zig programming language. We'll cover installation, project setup, implementation, and testing.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Installing Zig&lt;/li&gt;
&lt;li&gt;Project Setup&lt;/li&gt;
&lt;li&gt;Understanding the Code&lt;/li&gt;
&lt;li&gt;Building and Running&lt;/li&gt;
&lt;li&gt;Testing&lt;/li&gt;
&lt;li&gt;Common Issues&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Installing Zig
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Visit &lt;a href="https://ziglang.org/download/" rel="noopener noreferrer"&gt;ziglang.org/download&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Download the appropriate version for your operating system (0.11.0 or later)&lt;/li&gt;
&lt;li&gt;Extract the archive to a location of your choice&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add Zig to your system's PATH:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows&lt;/strong&gt;: Edit system environment variables and add the path to Zig&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linux/MacOS&lt;/strong&gt;: Add to ~/.bashrc or ~/.zshrc:
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;:/path/to/zig
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify installation:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   zig version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Project Setup
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create a new project directory:
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Initialize the project structure:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;mkdir &lt;/span&gt;src
   &lt;span class="nb"&gt;touch &lt;/span&gt;src/calculator.zig
   &lt;span class="nb"&gt;touch &lt;/span&gt;src/calculator_test.zig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create a build.zig file:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;   &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;@import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"std"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

   &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;standardTargetOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{});&lt;/span&gt;
       &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;optimize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;standardOptimizeOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{});&lt;/span&gt;

       &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;exe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addExecutable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"calculator"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;root_source_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"src/calculator.zig"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;optimize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;optimize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="p"&gt;});&lt;/span&gt;

       &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;installArtifact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exe&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

       &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;run_cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addRunArtifact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exe&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       &lt;span class="n"&gt;run_cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dependOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstallStep&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

       &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;run_step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Run the app"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       &lt;span class="n"&gt;run_step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dependOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;run_cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;step&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

       &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;exe_unit_tests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;root_source_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"src/calculator_test.zig"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;optimize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;optimize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="p"&gt;});&lt;/span&gt;

       &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;run_exe_unit_tests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addRunArtifact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exe_unit_tests&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;test_step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Run unit tests"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
       &lt;span class="n"&gt;test_step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dependOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;run_exe_unit_tests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;step&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Understanding the Code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Calculator Implementation (src/calculator.zig)
&lt;/h3&gt;

&lt;p&gt;The calculator consists of two main parts:&lt;/p&gt;

&lt;p&gt;a) The calculate function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;f64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sc"&gt;'+'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;num1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;num2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sc"&gt;'-'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;num1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;num2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sc"&gt;'*'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;num1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;num2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sc"&gt;'/'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;DivisionByZero&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;num1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;num2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sc"&gt;'%'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;@mod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;InvalidOperation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Takes two numbers and an operation as input&lt;/li&gt;
&lt;li&gt;Uses a switch statement to perform the calculation&lt;/li&gt;
&lt;li&gt;Handles errors like division by zero&lt;/li&gt;
&lt;li&gt;Returns the result as a f64 (double-precision float)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStdOut&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;stdin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStdIn&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Calculator (enter 'q' to quit)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Enter first number: "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;first_input_buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;first_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readUntilDelimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;first_input_buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;len&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;first_input&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="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;num1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;first_input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Enter operation (+, -, *, /): "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;op_buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readUntilDelimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;op_buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;len&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error: No operation entered!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Enter second number: "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;second_input_buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;num2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readUntilDelimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;second_input_buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;DivisionByZero&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error: Division by zero!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;InvalidOperation&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error: Invalid operation '{s}'!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Result: {d}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;b) The main function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates an interactive command-line interface&lt;/li&gt;
&lt;li&gt;Reads user input for numbers and operations&lt;/li&gt;
&lt;li&gt;Handles errors and displays results&lt;/li&gt;
&lt;li&gt;Provides a quit option ('q')&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Testing (src/calculator_test.zig)
&lt;/h3&gt;

&lt;p&gt;Tests are written using Zig's built-in testing framework:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight zig"&gt;&lt;code&gt;&lt;span class="k"&gt;test&lt;/span&gt; &lt;span class="s"&gt;"basic addition"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'+'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expectEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;@as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key testing concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each test is a function marked with the &lt;code&gt;test&lt;/code&gt; keyword&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;try&lt;/code&gt; for error handling&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;testing.expectEqual()&lt;/code&gt; for assertions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building and Running
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Build the project:
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Run the calculator:
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Using the calculator:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   Calculator (enter 'q' to quit)
   Enter first number: 10
   Enter operation (+, -, *, /): +
   Enter second number: 5
   Result: 15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;Run the test suite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zig build &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tests will verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic arithmetic operations&lt;/li&gt;
&lt;li&gt;Error handling for division by zero&lt;/li&gt;
&lt;li&gt;Invalid operation handling&lt;/li&gt;
&lt;li&gt;Edge cases&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common Issues
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Compilation Errors&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Ensure Zig version 0.11.0 or later&lt;/li&gt;
&lt;li&gt;Check file paths in build.zig&lt;/li&gt;
&lt;li&gt;Verify syntax, especially in error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Runtime Errors&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Division by zero is handled with error.DivisionByZero&lt;/li&gt;
&lt;li&gt;Invalid operations return error.InvalidOperation&lt;/li&gt;
&lt;li&gt;Input parsing errors are caught and handled&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build Issues&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Make sure build.zig is in the root directory&lt;/li&gt;
&lt;li&gt;Verify project structure matches the tutorial&lt;/li&gt;
&lt;li&gt;Check that all source files are present&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;To enhance the calculator, consider:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Adding more operations (power, square root)&lt;/li&gt;
&lt;li&gt;Implementing memory functions&lt;/li&gt;
&lt;li&gt;Supporting complex numbers&lt;/li&gt;
&lt;li&gt;Adding a graphical user interface&lt;/li&gt;
&lt;li&gt;Implementing scientific calculator features&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ziglang.org/documentation/master/" rel="noopener noreferrer"&gt;Zig Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ziglang.org/documentation/master/std/" rel="noopener noreferrer"&gt;Zig Standard Library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://discord.gg/zig" rel="noopener noreferrer"&gt;Zig Discord Community&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/augusthottie/calculator.git" rel="noopener noreferrer"&gt;Project Repo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;You've now built a functional command-line calculator in Zig! This project demonstrates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic Zig syntax and features&lt;/li&gt;
&lt;li&gt;Error handling&lt;/li&gt;
&lt;li&gt;Testing&lt;/li&gt;
&lt;li&gt;Command-line I/O&lt;/li&gt;
&lt;li&gt;Project organization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep exploring Zig's features and build upon this foundation to create more complex applications!&lt;/p&gt;

&lt;p&gt;Find me on X and Discord &lt;a class="mentioned-user" href="https://dev.to/augusthottie"&gt;@augusthottie&lt;/a&gt; &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Messaging System with RabbitMQ, Celery, and Flask</title>
      <dc:creator>augusthottie</dc:creator>
      <pubDate>Fri, 02 Aug 2024 17:14:26 +0000</pubDate>
      <link>https://forem.com/augusthottie/messaging-system-with-rabbitmq-celery-and-flask-2f8n</link>
      <guid>https://forem.com/augusthottie/messaging-system-with-rabbitmq-celery-and-flask-2f8n</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;This article walks you through setting up a messaging system using Flask, RabbitMQ, and Celery. The system handles asynchronous tasks like sending emails and logging timestamps. You can host the application on an AWS EC2 instance and use &lt;code&gt;screen&lt;/code&gt; to keep it running persistently, while exposing it to the internet using ngrok.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Repo
&lt;/h3&gt;

&lt;p&gt;For code details visit my repo ⬇️:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/AugustHottie/devops-stage3" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Features&lt;/li&gt;
&lt;li&gt;Technologies Used&lt;/li&gt;
&lt;li&gt;Installation&lt;/li&gt;
&lt;li&gt;Configuration&lt;/li&gt;
&lt;li&gt;Running the Application&lt;/li&gt;
&lt;li&gt;Endpoints&lt;/li&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Using ngrok&lt;/li&gt;
&lt;li&gt;Hosting on AWS EC2&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Send Emails&lt;/strong&gt;: Use the &lt;code&gt;?sendmail&lt;/code&gt; parameter to send emails via SMTP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log Timestamps&lt;/strong&gt;: Use the &lt;code&gt;?talktome&lt;/code&gt; parameter to log the current time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;View Logs&lt;/strong&gt;: Access application logs via the &lt;code&gt;/logs&lt;/code&gt; endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asynchronous Tasks&lt;/strong&gt;: Manage tasks asynchronously with RabbitMQ and Celery.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technologies Used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flask&lt;/strong&gt;: A lightweight framework for building web applications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Celery&lt;/strong&gt;: An asynchronous task queue for managing background tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RabbitMQ&lt;/strong&gt;: A messaging broker that handles message queuing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SMTP&lt;/strong&gt;: Protocol used for sending emails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ngrok&lt;/strong&gt;: A tool to expose your local server to the internet.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.7 or higher&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pip&lt;/code&gt; (Python package installer)&lt;/li&gt;
&lt;li&gt;RabbitMQ server installed and running&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1: Clone the Repository
&lt;/h3&gt;

&lt;p&gt;First, clone the repository and navigate to the project directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/AugustHottie/devops-stage3.git
&lt;span class="nb"&gt;cd &lt;/span&gt;devops-stage3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Create a Virtual Environment
&lt;/h3&gt;

&lt;p&gt;Create and activate a virtual environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate  &lt;span class="c"&gt;# On Windows use `venv\Scripts\activate`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Install Dependencies
&lt;/h3&gt;

&lt;p&gt;Install the necessary packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Set Up Environment Variables&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file or set the variables directly in your shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   MAIL_ADDRESS=your-email@gmail.com
   APP_PASSWORD=your-google-app-password
   LOG_FILE_PATH=/var/log/messaging_system.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Ensure RabbitMQ is Running&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Start the RabbitMQ server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start rabbitmq-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Step 1: Start the Celery Worker
&lt;/h3&gt;

&lt;p&gt;Open a new terminal, activate your virtual environment, and start the Celery worker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;celery &lt;span class="nt"&gt;-A&lt;/span&gt; app.celery worker &lt;span class="nt"&gt;--loglevel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Start the Flask Application
&lt;/h3&gt;

&lt;p&gt;In another terminal window, run the Flask application:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Endpoints
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Example Usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Main route to send emails or log time&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http://localhost:8000/?sendmail=your_email@example.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Main route to log the current time&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http://localhost:8000/?talktome&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/logs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;View application logs&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http://localhost:8000/logs&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Logging
&lt;/h2&gt;

&lt;p&gt;Logs are saved to the path specified by &lt;code&gt;LOG_FILE_PATH&lt;/code&gt;. Make sure the application has permission to write to this location.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using ngrok
&lt;/h2&gt;

&lt;p&gt;To expose your local application to the internet, follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Download and Install ngrok&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   wget https://bin.equinox.io/c/4b0e5f0d1d6e/ngrok-stable-linux-amd64.zip
   unzip ngrok-stable-linux-amd64.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Add Your ngrok Authtoken&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sign up or log in to your &lt;a href="https://dashboard.ngrok.com/signup" rel="noopener noreferrer"&gt;ngrok account&lt;/a&gt; to get your authtoken. Once you have it, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   ./ngrok authtoken your_ngrok_auth_token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;your_ngrok_auth_token&lt;/code&gt; with the token you received from the ngrok dashboard.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Expose Your Flask Application&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   ./ngrok http 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Use the Provided ngrok URL&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use the URL provided by ngrok to access your application externally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosting on AWS EC2
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Launch an EC2 Instance
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Log in to AWS Management Console&lt;/strong&gt; and navigate to EC2.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Launch a new EC2 instance&lt;/strong&gt; with your preferred configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure security groups&lt;/strong&gt; to allow HTTP (port 80) and custom TCP (port 8000) traffic.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Use SSH to connect to your EC2 instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; /path/to/your-key.pem ec2-user@your-ec2-public-dns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Install Dependencies on EC2
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Update the package list and install necessary packages:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;yum update &lt;span class="nt"&gt;-y&lt;/span&gt;
   &lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; python3-pip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clone the repository, create a virtual environment, and install dependencies:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   git clone https://github.com/AugustHottie/devops-stage3.git
   &lt;span class="nb"&gt;cd &lt;/span&gt;devops-stage3
   python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
   &lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate
   pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ensure RabbitMQ is installed and running:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; rabbitmq-server
   &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start rabbitmq-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Set Up &lt;code&gt;screen&lt;/code&gt; to Persist the Application
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install &lt;code&gt;screen&lt;/code&gt; if it is not already installed:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; screen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start a new screen session:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   screen &lt;span class="nt"&gt;-S&lt;/span&gt; myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run the Flask application within the screen session:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Detach from the screen session by pressing &lt;code&gt;Ctrl+A&lt;/code&gt;, then &lt;code&gt;D&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;To reattach to the screen session:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   screen &lt;span class="nt"&gt;-r&lt;/span&gt; myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Set Up ngrok on EC2
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Download and install ngrok:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   wget https://bin.equinox.io/c/4b0e5f0d1d6e/ngrok-stable-linux-amd64.zip
   unzip ngrok-stable-linux-amd64.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add your ngrok authtoken:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   ./ngrok authtoken your_ngrok_auth_token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;your_ngrok_auth_token&lt;/code&gt; with the token you received from the ngrok dashboard.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Expose your Flask application using ngrok:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   ./ngrok http 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use the provided ngrok URL&lt;/strong&gt; to access your application externally.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This guide should help you get your messaging system up and running both locally and on an AWS EC2 instance. Feel free to share your feedback or suggestions in the comment section! 🚀&lt;/p&gt;

</description>
      <category>flask</category>
      <category>celery</category>
      <category>devops</category>
    </item>
    <item>
      <title>Introducing DevOpsFetch: A Comprehensive Server Monitoring Tool</title>
      <dc:creator>augusthottie</dc:creator>
      <pubDate>Fri, 02 Aug 2024 15:59:09 +0000</pubDate>
      <link>https://forem.com/augusthottie/introducing-devopsfetch-a-comprehensive-server-monitoring-tool-45o2</link>
      <guid>https://forem.com/augusthottie/introducing-devopsfetch-a-comprehensive-server-monitoring-tool-45o2</guid>
      <description>&lt;p&gt;Welcome to DevOpsFetch! This is a Bash tool designed to streamline the process of retrieving and monitoring server information. With DevOpsFetch, you can easily display active ports, user logins, Nginx configurations, Docker images, and container statuses. Additionally, the tool includes a systemd service for continuous monitoring and logging, ensuring your server remains under constant observation.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Features&lt;/li&gt;
&lt;li&gt;
Installation

&lt;ul&gt;
&lt;li&gt;Dependencies&lt;/li&gt;
&lt;li&gt;Setup&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Usage

&lt;ul&gt;
&lt;li&gt;Display Active Ports&lt;/li&gt;
&lt;li&gt;Port Information&lt;/li&gt;
&lt;li&gt;Docker Information&lt;/li&gt;
&lt;li&gt;Nginx Information&lt;/li&gt;
&lt;li&gt;User Logins&lt;/li&gt;
&lt;li&gt;Time Range Activities&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Logging&lt;/li&gt;

&lt;li&gt;Help&lt;/li&gt;

&lt;li&gt;Full Script Code&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;p&gt;DevOpsFetch offers a suite of features to help you monitor and manage your server effectively:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Display all active ports and services&lt;/li&gt;
&lt;li&gt;Provide detailed information about a specific port&lt;/li&gt;
&lt;li&gt;List all Docker images and containers&lt;/li&gt;
&lt;li&gt;Provide detailed information about a specific Docker container&lt;/li&gt;
&lt;li&gt;Display all Nginx domains and their ports&lt;/li&gt;
&lt;li&gt;Provide detailed configuration information for a specific Nginx domain&lt;/li&gt;
&lt;li&gt;List all users and their last login times&lt;/li&gt;
&lt;li&gt;Provide detailed information about a specific user&lt;/li&gt;
&lt;li&gt;Display activities within a specified time range&lt;/li&gt;
&lt;li&gt;Continuous monitoring and logging with log rotation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Dependencies
&lt;/h3&gt;

&lt;p&gt;Before you begin, ensure the following packages are installed on your system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;net-tools&lt;/code&gt; for &lt;code&gt;netstat&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker.io&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nginx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jq&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;finger&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clone the repository:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   git clone https://github.com/AugustHottie/devopsfetch-stage5a.git
   &lt;span class="nb"&gt;cd &lt;/span&gt;devopsfetch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run the installation script to set up dependencies and the systemd service:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;/div&gt;



&lt;p&gt;This script will install the necessary dependencies, set up the &lt;code&gt;devopsfetch&lt;/code&gt; command, and enable the &lt;code&gt;devopsfetch&lt;/code&gt; systemd service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Command-line Options
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-p, --port&lt;/code&gt; : Display all active ports and services.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-p &amp;lt;port_number&amp;gt;&lt;/code&gt; : Display detailed information about a specific port.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-d, --docker&lt;/code&gt; : List all Docker images and containers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-d &amp;lt;container_name&amp;gt;&lt;/code&gt; : Display detailed information about a specific Docker container.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-n, --nginx&lt;/code&gt; : Display all Nginx domains and their ports.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-n &amp;lt;domain&amp;gt;&lt;/code&gt; : Display detailed configuration information for a specific Nginx domain.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-u, --users&lt;/code&gt; : List all users and their last login times.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-u &amp;lt;username&amp;gt;&lt;/code&gt; : Display detailed information about a specific user.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-t, --time &amp;lt;start&amp;gt; &amp;lt;end&amp;gt;&lt;/code&gt; : Display activities within a specified time range.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-h, --help&lt;/code&gt; : Display usage instructions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Display Active Ports
&lt;/h3&gt;

&lt;p&gt;To display all active ports and services, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./devopsfetch.sh &lt;span class="nt"&gt;-p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Port Information
&lt;/h3&gt;

&lt;p&gt;To provide detailed information about a specific port, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./devopsfetch.sh &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;port_number&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./devopsfetch.sh &lt;span class="nt"&gt;-p&lt;/span&gt; 80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Docker Information
&lt;/h3&gt;

&lt;p&gt;To list all Docker images and containers, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./devopsfetch.sh &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To provide detailed information about a specific Docker container, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./devopsfetch.sh &lt;span class="nt"&gt;-d&lt;/span&gt; &amp;lt;container_name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./devopsfetch.sh &lt;span class="nt"&gt;-d&lt;/span&gt; my_container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Nginx Information
&lt;/h3&gt;

&lt;p&gt;To display all Nginx domains and their ports, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./devopsfetch.sh &lt;span class="nt"&gt;-n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To provide detailed configuration information for a specific Nginx domain, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./devopsfetch.sh &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;domain&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./devopsfetch.sh &lt;span class="nt"&gt;-n&lt;/span&gt; example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  User Logins
&lt;/h3&gt;

&lt;p&gt;To list all users and their last login times, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./devopsfetch.sh &lt;span class="nt"&gt;-u&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To provide detailed information about a specific user, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./devopsfetch.sh &lt;span class="nt"&gt;-u&lt;/span&gt; &amp;lt;username&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./devopsfetch.sh &lt;span class="nt"&gt;-u&lt;/span&gt; myuser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Time Range Activities
&lt;/h3&gt;

&lt;p&gt;To display activities within a specified time range, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./devopsfetch.sh &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;start_time&amp;gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;end_time&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./devopsfetch.sh &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"2024-07-21 00:00:00"&lt;/span&gt; &lt;span class="s2"&gt;"2024-07-22 00:00:00"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Logging
&lt;/h2&gt;

&lt;p&gt;Logs are stored in the &lt;code&gt;/var/log/devopsfetch.log&lt;/code&gt; directory. Log rotation is implemented to manage log file size. Logs are rotated daily, keeping up to 7 days of logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Help
&lt;/h2&gt;

&lt;p&gt;To display the help message with usage instructions, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./devopsfetch.sh &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Full Script Code
&lt;/h2&gt;

&lt;p&gt;To view the full script code, check my GitHub repo: &lt;a href="https://github.com/AugustHottie/devopsfetch-stage5a" rel="noopener noreferrer"&gt;DevOpsFetch GitHub Repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By following this guide, you should be able to effectively install, configure, and use DevOpsFetch to monitor your server. For any issues or questions, refer to the comment section or consult the script documentation on my GitHub. Happy monitoring!🔍🚀&lt;/p&gt;

</description>
      <category>bash</category>
      <category>tooling</category>
      <category>linux</category>
    </item>
    <item>
      <title>How to Deploy a Static Website on AWS EC2 Using Apache</title>
      <dc:creator>augusthottie</dc:creator>
      <pubDate>Fri, 02 Aug 2024 15:42:47 +0000</pubDate>
      <link>https://forem.com/augusthottie/how-to-deploy-a-static-website-on-aws-ec2-using-apache-23f2</link>
      <guid>https://forem.com/augusthottie/how-to-deploy-a-static-website-on-aws-ec2-using-apache-23f2</guid>
      <description>&lt;p&gt;Deploying a static website on an AWS EC2 instance using Apache is a straightforward process that you can follow to get your site live. This guide will walk you through each step using an example where the static website includes an HTML file hosted on GitHub. The example website mentions the HNG Internship which I took part in, and contains a link to &lt;a href="https://hng.tech" rel="noopener noreferrer"&gt;https://hng.tech&lt;/a&gt;, but you can adapt these steps to deploy any static website.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Setup AWS EC2 Instance&lt;/li&gt;
&lt;li&gt;Install and Configure Apache&lt;/li&gt;
&lt;li&gt;Deploy the Static Website&lt;/li&gt;
&lt;li&gt;Access the Website&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Deploying a static website on an AWS EC2 instance using Apache is a straightforward process. This guide will walk you through each step, ensuring your website is live and accessible. The static website includes an HTML file hosted on GitHub, mentioning the HNG Internship and linking to &lt;a href="https://hng.tech" rel="noopener noreferrer"&gt;https://hng.tech&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Before you begin, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An AWS account&lt;/li&gt;
&lt;li&gt;Basic knowledge of AWS EC2 and SSH&lt;/li&gt;
&lt;li&gt;An SSH key pair for accessing the EC2 instance&lt;/li&gt;
&lt;li&gt;A static website (HTML, CSS, JavaScript files)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup AWS EC2 Instance
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Launch an EC2 Instance
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log in to your AWS Management Console.&lt;/li&gt;
&lt;li&gt;Navigate to the EC2 Dashboard.&lt;/li&gt;
&lt;li&gt;Click on "Launch Instance".&lt;/li&gt;
&lt;li&gt;Choose an Amazon Machine Image (AMI) (e.g., Amazon Linux 2 AMI).&lt;/li&gt;
&lt;li&gt;Select an Instance Type (e.g., t2.micro for free tier eligibility).&lt;/li&gt;
&lt;li&gt;Configure Instance Details (default settings are usually fine).&lt;/li&gt;
&lt;li&gt;Add Storage (default settings are fine).&lt;/li&gt;
&lt;li&gt;Add Tags (optional).&lt;/li&gt;
&lt;li&gt;Configure Security Group:

&lt;ul&gt;
&lt;li&gt;Add a rule to allow HTTP traffic on port 80.&lt;/li&gt;
&lt;li&gt;Add a rule to allow SSH traffic on port 22.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Review and Launch the instance.&lt;/li&gt;
&lt;li&gt;Select your SSH key pair for the instance.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2. Connect to the EC2 Instance
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open a terminal (or use PuTTY if on Windows).&lt;/li&gt;
&lt;li&gt;Connect to your instance using SSH:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   ssh &lt;span class="nt"&gt;-i&lt;/span&gt; /path/to/your-key-pair.pem ec2-user@your-instance-public-ip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install and Configure Apache
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Update Packages
&lt;/h3&gt;

&lt;p&gt;First, update your package list to ensure you have the latest versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum update &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Install the Apache web server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;httpd &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Start Apache
&lt;/h3&gt;

&lt;p&gt;Start the Apache service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start httpd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Enable Apache to Start on Boot
&lt;/h3&gt;

&lt;p&gt;Enable Apache to start on boot to ensure it runs automatically when the instance is restarted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;httpd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploy the Static Website
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Navigate to the Apache Directory
&lt;/h3&gt;

&lt;p&gt;Change the directory to where Apache serves files:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Download the &lt;code&gt;index.html&lt;/code&gt; File Using &lt;code&gt;wget&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Download your HTML file from GitHub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;wget https://raw.githubusercontent.com/AugustHottie/devops-task0/master/index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Set the Correct Permissions
&lt;/h3&gt;

&lt;p&gt;Set the appropriate permissions for the &lt;code&gt;index.html&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chown &lt;/span&gt;apache:apache /var/www/html/index.html
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;644 /var/www/html/index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Access the Website
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Open a Web Browser
&lt;/h3&gt;

&lt;p&gt;Navigate to the public IP address of your EC2 instance. Your static website should be displayed. In our example, it mentions the HNG Internship and links to &lt;a href="https://hng.tech" rel="noopener noreferrer"&gt;https://hng.tech&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;By following the steps outlined in this guide, you can successfully deploy any static website on an AWS EC2 instance using Apache. Your website should now be accessible via the public IP address of your EC2 instance. This guide uses an example from an HNG Internship task, but the steps are adaptable to any static site!&lt;/p&gt;

&lt;p&gt;If you have any questions or need further assistance, feel free to reach out. Happy deploying!🚀&lt;/p&gt;

</description>
      <category>aws</category>
      <category>webdeployment</category>
      <category>awsec2</category>
      <category>cloud</category>
    </item>
    <item>
      <title>User Management Automation : Bash Script Guide</title>
      <dc:creator>augusthottie</dc:creator>
      <pubDate>Mon, 01 Jul 2024 18:46:28 +0000</pubDate>
      <link>https://forem.com/augusthottie/user-management-automation-bash-script-guide-14pl</link>
      <guid>https://forem.com/augusthottie/user-management-automation-bash-script-guide-14pl</guid>
      <description>&lt;p&gt;Trying to manage user accounts in a Linux environment can be a stressful, time-consuming and error-prone process, especially in a large organization with many users. Automating this process not only makes things easy, efficient and time-saving, but also ensures consistency and accuracy. In this article, we'll look at a Bash script designed to automate user creation, group assignment and password management. This script reads from a file containing user information, creates users and groups as specified, sets up home directories with appropriate permissions, generates random passwords and logs all actions. &lt;/p&gt;

&lt;h2&gt;
  
  
  Script Overview
&lt;/h2&gt;

&lt;p&gt;Here's a breakdown of the bash script used to automate the user management process:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c"&gt;# Constants for the script&lt;/span&gt;
&lt;span class="nv"&gt;SECURE_FOLDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/secure"&lt;/span&gt;  &lt;span class="c"&gt;# The path to the secure folder where user information will be stored&lt;/span&gt;
&lt;span class="nv"&gt;LOG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/log/user_management.log"&lt;/span&gt;  &lt;span class="c"&gt;# The path to the log file for recording script execution&lt;/span&gt;
&lt;span class="nv"&gt;PASSWORD_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/secure/user_passwords.csv"&lt;/span&gt;  &lt;span class="c"&gt;# The path to the file where user passwords will be stored&lt;/span&gt;

log_message&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;# Function to log a message with a timestamp to the log file&lt;/span&gt;
    &lt;span class="c"&gt;# Arguments:&lt;/span&gt;
    &lt;span class="c"&gt;#   $1: The message to be logged&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%Y-%m-%d %H:%M:%S'&lt;/span&gt;&lt;span class="si"&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;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Function to generate a random password&lt;/span&gt;
generate_password&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-dc&lt;/span&gt; &lt;span class="s1"&gt;'A-Za-z0-9!@#$%^&amp;amp;*()-_'&lt;/span&gt; &amp;lt; /dev/urandom | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 16
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SECURE_FOLDER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# Check if the secure folder exists, if not, create it&lt;/span&gt;
    &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$SECURE_FOLDER&lt;/span&gt;
    log_message &lt;span class="s2"&gt;"Secure folder created."&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Check for command-line argument&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="nt"&gt;-ne&lt;/span&gt; 1 &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;"Usage: &lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;path_to_user_file&amp;gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;USER_FILE&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="c"&gt;# Check if file exists&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"File not found: &lt;/span&gt;&lt;span class="nv"&gt;$USER_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Create the log file and password file if they do not exist&lt;/span&gt;
&lt;span class="nb"&gt;touch&lt;/span&gt; &lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;
&lt;span class="nb"&gt;touch&lt;/span&gt; &lt;span class="nv"&gt;$PASSWORD_FILE&lt;/span&gt;

&lt;span class="c"&gt;# Set the permissions on the password file to be read/write only by the user executing the script&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 &lt;span class="nv"&gt;$PASSWORD_FILE&lt;/span&gt;

&lt;span class="c"&gt;# Write the header to the password file if it is empty&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;$PASSWORD_FILE&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;"Username,Password"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$PASSWORD_FILE&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Add new line to USER_FILE to avoid error in while loop&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$USER_FILE&lt;/span&gt;

&lt;span class="c"&gt;# read one by one, there is no seperator so it will read line by line&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; line&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c"&gt;# Trim whitespace, and seperate them via ;&lt;/span&gt;
    &lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&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;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | xargs | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;';'&lt;/span&gt; &lt;span class="nt"&gt;-f1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&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;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | xargs | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;';'&lt;/span&gt; &lt;span class="nt"&gt;-f2&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;# Skip empty lines&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        continue
    fi&lt;/span&gt;

    &lt;span class="c"&gt;# Create user group (personal group)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; getent group &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;groupadd &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        log_message &lt;span class="s2"&gt;"Group '&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;' created."&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;log_message &lt;span class="s2"&gt;"Group '&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;' already exists."&lt;/span&gt;
    &lt;span class="k"&gt;fi&lt;/span&gt;

    &lt;span class="c"&gt;# Create user if not exists&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;id&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;useradd &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /bin/bash &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        log_message &lt;span class="s2"&gt;"User '&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;' created with home directory."&lt;/span&gt;

        &lt;span class="c"&gt;# Generate and set password&lt;/span&gt;
        &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;generate_password&lt;span class="si"&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;$username&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$password&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | chpasswd
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$password&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$PASSWORD_FILE&lt;/span&gt;
        log_message &lt;span class="s2"&gt;"Password set for user '&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;'."&lt;/span&gt;

        &lt;span class="c"&gt;# Set permissions for home directory&lt;/span&gt;
        &lt;span class="nb"&gt;chmod &lt;/span&gt;700 &lt;span class="s2"&gt;"/home/&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"/home/&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        log_message &lt;span class="s2"&gt;"Home directory permissions set for user '&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;'."&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;log_message &lt;span class="s2"&gt;"User '&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;' already exists."&lt;/span&gt;
    &lt;span class="k"&gt;fi&lt;/span&gt;

    &lt;span class="c"&gt;# Add user to additional groups&lt;/span&gt;
    &lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-ra&lt;/span&gt; group_array &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$groups&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;for &lt;/span&gt;group &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;group_array&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="nv"&gt;group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&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;$group&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | xargs&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# Trim whitespace&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
                if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; getent group &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
                    &lt;/span&gt;groupadd &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
                    log_message &lt;span class="s2"&gt;"Group '&lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt;' created."&lt;/span&gt;
                &lt;span class="k"&gt;fi
                &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
                log_message &lt;span class="s2"&gt;"User '&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;' added to group '&lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt;'."&lt;/span&gt;
            &lt;span class="k"&gt;fi
    done
done&lt;/span&gt; &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Script Breakdown
&lt;/h2&gt;

&lt;p&gt;Let's break down the key components of this script and understand how they work together to automate user management.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setting up directories and files 
The script begins by defining the locations of the secure folder, log file and password file:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;SECURE_FOLDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/secure"&lt;/span&gt;
&lt;span class="nv"&gt;LOG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/log/user_management.log"&lt;/span&gt;
&lt;span class="nv"&gt;PASSWORD_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/secure/user_passwords.csv"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A function, log_message(), is used to log messages with a timestamp:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;log_message&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%Y-%m-%d %H:%M:%S'&lt;/span&gt;&lt;span class="si"&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;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script checks if the secure folder exists and creates it if it doesn't:&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="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SECURE_FOLDER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$SECURE_FOLDER&lt;/span&gt;
    log_message &lt;span class="s2"&gt;"Secure folder created."&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Validating Input&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The script expects a single argument: the path to the user file. It checks if the argument is provided and if the file exists:&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="nt"&gt;-ne&lt;/span&gt; 1 &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;"Usage: &lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;path_to_user_file&amp;gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;USER_FILE&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;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"File not found: &lt;/span&gt;&lt;span class="nv"&gt;$USER_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Preparing Log and Password Files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The script creates and secures the log and password files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch&lt;/span&gt; &lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;
&lt;span class="nb"&gt;touch&lt;/span&gt; &lt;span class="nv"&gt;$PASSWORD_FILE&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 &lt;span class="nv"&gt;$PASSWORD_FILE&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;$PASSWORD_FILE&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;"Username,Password"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$PASSWORD_FILE&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Processing the User File&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main part of the script reads the user file line by line, processes each user, and performs the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read Username and Groups: It reads the username and groups from each line, trimming any whitespace:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;';'&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; username &lt;span class="nb"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$username&lt;/span&gt; | xargs&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$groups&lt;/span&gt; | xargs&lt;span class="si"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        continue
    fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create User Group: If the user's personal group does not exist, it creates the group:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; getent group &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;groupadd &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    log_message &lt;span class="s2"&gt;"Group '&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;' created."&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;log_message &lt;span class="s2"&gt;"Group '&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;' already exists."&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create User: If the user does not exist, it creates the user with a home directory and assigns the personal group. It also generates a random password, sets it for the user, and secures the home directory:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;useradd &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /bin/bash &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    log_message &lt;span class="s2"&gt;"User '&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;' created with home directory."&lt;/span&gt;

    &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-dc&lt;/span&gt; &lt;span class="s1"&gt;'A-Za-z0-9!@#$%^&amp;amp;*()-_'&lt;/span&gt; &amp;lt; /dev/urandom | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 16&lt;span class="si"&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;$username&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$password&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | chpasswd
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$password&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$PASSWORD_FILE&lt;/span&gt;
    log_message &lt;span class="s2"&gt;"Password set for user '&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;'."&lt;/span&gt;

    &lt;span class="nb"&gt;chmod &lt;/span&gt;700 &lt;span class="s2"&gt;"/home/&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"/home/&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    log_message &lt;span class="s2"&gt;"Home directory permissions set for user '&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;'."&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;log_message &lt;span class="s2"&gt;"User '&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;' already exists."&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Assign Groups: The script reads the additional groups for the user and assigns them, creating any missing groups:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-ra&lt;/span&gt; group_array &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$groups&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;group &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;group_array&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="nv"&gt;group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$group&lt;/span&gt; | xargs&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; getent group &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;groupadd &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        log_message &lt;span class="s2"&gt;"Group '&lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt;' created."&lt;/span&gt;
    &lt;span class="k"&gt;fi
    &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    log_message &lt;span class="s2"&gt;"User '&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;' added to group '&lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt;'."&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Finalize: The script ensures the password file is secure:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;600 &lt;span class="nv"&gt;$PASSWORD_FILE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above only allows the authorized user (executor) to read the contents of the password csv file.&lt;/p&gt;

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

&lt;p&gt;This script provides a robust solution for managing user accounts in a Linux environment. By automating user creation, group assignment, and password management, it ensures consistency and saves valuable time for system administrators. The use of logging and secure password storage further enhances the reliability and security of the user management process.&lt;/p&gt;

&lt;p&gt;For more information about automation and system administration, check out the HNG Internship program at &lt;a href="https://hng.tech/internship" rel="noopener noreferrer"&gt;HNG Internship&lt;/a&gt;. The program offers a wealth of resources and opportunities for aspiring DevOps engineers. Additionally, you can explore hiring opportunities at &lt;a href="https://hng.tech/hire" rel="noopener noreferrer"&gt;HNG Hire&lt;/a&gt; and premium services at &lt;a href="https://hng.tech/premium" rel="noopener noreferrer"&gt;HNG Premium&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By implementing such automation scripts, you can streamline your operations, reduce errors, and improve overall system security and efficiency.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>sysops</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
