<?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: Femi</title>
    <description>The latest articles on Forem by Femi (@oofemi).</description>
    <link>https://forem.com/oofemi</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%2F3870568%2F4a913260-4184-4308-b879-4b39d49abd6d.jpeg</url>
      <title>Forem: Femi</title>
      <link>https://forem.com/oofemi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/oofemi"/>
    <language>en</language>
    <item>
      <title>Scaling Observability: Designing a Resilient Multi-Node Monitoring Stack with Docker, Prometheus &amp; Grafana</title>
      <dc:creator>Femi</dc:creator>
      <pubDate>Fri, 15 May 2026 07:27:49 +0000</pubDate>
      <link>https://forem.com/oofemi/scaling-observability-designing-a-resilient-multi-node-monitoring-stack-with-docker-prometheus--4ilj</link>
      <guid>https://forem.com/oofemi/scaling-observability-designing-a-resilient-multi-node-monitoring-stack-with-docker-prometheus--4ilj</guid>
      <description>&lt;p&gt;Building a monitoring environment on a local machine is a great weekend project, but scaling it up to look after a live fleet of remote servers requires shifts in how you handle configuration stability, dashboard variables, and hardware persistence.&lt;br&gt;
In this post, I want to walk through how I configured and optimized a multi-node monitoring stack utilizing Prometheus, Node Exporter, and Grafana deployed entirely via Docker Compose.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Deployment Architecture
To keep things clean and modular, the entire monitoring core runs as separate containerized microservices. The telemetry relies on bind-mounts to guarantee that if a container is wiped or updated, the custom target definitions stay safe on disk.
Here is the structural framework of the modern docker-compose.yml layout used to spin it up:
version: '3.8'&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;services:&lt;br&gt;
  prometheus:&lt;br&gt;
    image: prom/prometheus:latest&lt;br&gt;
    container_name: prometheus&lt;br&gt;
    restart: always&lt;br&gt;
    volumes:&lt;br&gt;
      - ./prometheus:/etc/prometheus&lt;br&gt;
    ports:&lt;br&gt;
      - "9090:9090"&lt;/p&gt;

&lt;p&gt;grafana:&lt;br&gt;
    image: grafana/grafana:latest&lt;br&gt;
    container_name: grafana&lt;br&gt;
    restart: always&lt;br&gt;
    ports:&lt;br&gt;
      - "3000:3000"&lt;/p&gt;

&lt;p&gt;Solving the High-Availability Problem&lt;br&gt;
A common issue with basic Docker deployments is that if the physical or virtual host undergoes a sudden reboot or power failure, your container instances drop offline into an Exited state.&lt;br&gt;
By applying the restart: always policy under our services, the Docker daemon automatically handles relaunching the infrastructure as soon as the system initializes. No manual ssh intervention required.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scraping Multiple Remote Targets
Inside the prometheus.yml target profile, I pooled our infrastructure assets into distinct target blocks. Rather than hardcoding distinct jobs for every server, grouping identical server profiles under a singular array makes filtering exponentially cleaner
global:
scrape_interval: 15s&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;scrape_configs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;job_name: 'prometheus'&lt;br&gt;
static_configs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;targets: ['localhost:9090']&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;job_name: 'remote_ubuntu_nodes'&lt;br&gt;
static_configs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;targets: 

&lt;ul&gt;
&lt;li&gt;'192.168.23.87:9110'&lt;/li&gt;
&lt;li&gt;'192.168.23.88:9100'&lt;/li&gt;
&lt;li&gt;'192.168.23.89:9100'&lt;/li&gt;
&lt;li&gt;'192.168.23.90:9100'
Transitioning to a Fleet View in Grafana
Standard configurations for public dashboards (like the classic Node Exporter Full) default to strict single-select filters. When checking on multiple nodes like load balancers or app-services, clicking down an endless dropdown isn't sustainable.
To move to a comprehensive fleet view, we can tap into Dashboard Settings (s shortcut in Grafana) and adjust the query variables:
Multi-value selection: Enabled.
Include All option: Enabled.
To prevent the gauges from blending the metrics into a confusing average, you can open the row settings for your graphs and toggle Repeat For: Instance. Grafana will then dynamically duplicate that entire row of health metrics for every machine checking into the cluster.
&lt;img 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%2Fgk1hmfd1vffrbimvirvp.jpg" alt=" " width="800" height="412"&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>grafana</category>
      <category>prometheus</category>
      <category>docker</category>
    </item>
    <item>
      <title>Clean Code for DevOps: Refactoring my Ansible Lab into Roles</title>
      <dc:creator>Femi</dc:creator>
      <pubDate>Thu, 23 Apr 2026 16:33:51 +0000</pubDate>
      <link>https://forem.com/oofemi/clean-code-for-devops-refactoring-my-ansible-lab-into-roles-1j84</link>
      <guid>https://forem.com/oofemi/clean-code-for-devops-refactoring-my-ansible-lab-into-roles-1j84</guid>
      <description>&lt;p&gt;As my Ansible project grew, my single master playbook started to get crowded. Today, I decided to 'graduate' my automation by implementing Ansible Roles.&lt;br&gt;
​I’ve moved from a linear script to a modular directory structure:&lt;br&gt;
​/roles/web_servers&lt;br&gt;
​/roles/workstations&lt;br&gt;
​/roles/db_servers&lt;br&gt;
roles/file_servers&lt;/p&gt;

&lt;p&gt;​This refactor allows me to treat my infrastructure like LEGO blocks. Need a new web server? Just call the role. Want to update my workstation? The logic is isolated and safe.&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%2Flqmdju1mrydqclcvi368.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flqmdju1mrydqclcvi368.jpg" alt=" " width="578" height="760"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1vh8f9cn6p2jdkjivrg1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1vh8f9cn6p2jdkjivrg1.jpg" alt=" " width="603" height="830"&gt;&lt;/a&gt;&lt;br&gt;
​The biggest challenge? Managing file paths and ensuring the tasks/main.yml in each role was perfectly mapped. It’s a bit more setup time initially, but the long-term maintenance is now nearly zero.&lt;/p&gt;

</description>
      <category>ansible</category>
      <category>devops</category>
      <category>cloud</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Tool-Chain Automation: Using Ansible to Deploy Terraform and Web Content</title>
      <dc:creator>Femi</dc:creator>
      <pubDate>Tue, 14 Apr 2026 20:45:15 +0000</pubDate>
      <link>https://forem.com/oofemi/tool-chain-automation-using-ansible-to-deploy-terraform-and-web-content-1579</link>
      <guid>https://forem.com/oofemi/tool-chain-automation-using-ansible-to-deploy-terraform-and-web-content-1579</guid>
      <description>&lt;p&gt;Automation doesn't stop at OS updates. Today, I expanded my Ansible master playbook to handle two very different, but equally important, tasks:&lt;/p&gt;

&lt;p&gt;​Software Provisioning: Used the unarchive module to fetch, unzip, and install Terraform from a remote URL directly into /usr/local/bin. &lt;/p&gt;

&lt;p&gt;No manual downloads, no mess.&lt;br&gt;
​Content Orchestration: Deployed a custom HTML site across my web tier using the copy module, ensuring strict Linux permissions (0644) were applied automatically.&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%2F0q4gk51ur24ruvz8nc50.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%2F0q4gk51ur24ruvz8nc50.png" alt=" " width="500" height="548"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1wfmvjq3kjai4qtjcb93.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%2F1wfmvjq3kjai4qtjcb93.png" alt=" " width="800" height="360"&gt;&lt;/a&gt;&lt;br&gt;
​By combining package management (apt/dnf), remote resource fetching, and file distribution, I've created a single point of truth for my entire workstation and server fleet.&lt;br&gt;
​IaC isn't just about the servers; it's about the tools we use to build them! 🛠️"&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>ansible</category>
      <category>automation</category>
      <category>devops</category>
    </item>
    <item>
      <title>Surgical Automation: Mastering Ansible Tags for Multi-Tier Deployments</title>
      <dc:creator>Femi</dc:creator>
      <pubDate>Tue, 14 Apr 2026 15:57:38 +0000</pubDate>
      <link>https://forem.com/oofemi/surgical-automation-mastering-ansible-tags-for-multi-tier-deployments-2bkc</link>
      <guid>https://forem.com/oofemi/surgical-automation-mastering-ansible-tags-for-multi-tier-deployments-2bkc</guid>
      <description>&lt;p&gt;​Today, I implemented Ansible Tags to solve this.&lt;br&gt;
​By tagging my tasks ( tags: apache, tags: db), I now have 'surgical' control over my infrastructure. I can:&lt;br&gt;
​Update just the Web tier: ansible-playbook master.yml --tags "apache"&lt;br&gt;
​Deploy only Database changes: ansible-playbook master.yml --tags "db"&lt;/p&gt;

&lt;p&gt;​Skip the long update processes: ansible-playbook master.yml --skip-tags "always"&lt;/p&gt;

&lt;p&gt;​I also maintained my Multi-OS logic, ensuring my tags work seamlessly across both Ubuntu and CentOS nodes.&lt;/p&gt;

&lt;p&gt;​This taught me a valuable lesson in 'Developer Experience' (DX)—making my automation tools easy and fast.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzm69erbn9ua6asnq9wfj.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%2Fzm69erbn9ua6asnq9wfj.png" alt=" " width="577" height="652"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmte61axjs8p12kmp0lyi.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%2Fmte61axjs8p12kmp0lyi.png" alt=" " width="638" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ansible</category>
      <category>devops</category>
      <category>automation</category>
      <category>cloud</category>
    </item>
    <item>
      <title>From Scripts to Infrastructure-as-Code: Building a Multi-Tier Ansible Playbook</title>
      <dc:creator>Femi</dc:creator>
      <pubDate>Sun, 12 Apr 2026 17:35:15 +0000</pubDate>
      <link>https://forem.com/oofemi/from-scripts-to-infrastructure-as-code-building-a-multi-tier-ansible-playbook-9l2</link>
      <guid>https://forem.com/oofemi/from-scripts-to-infrastructure-as-code-building-a-multi-tier-ansible-playbook-9l2</guid>
      <description>&lt;p&gt;There is a moment in every DevOps journey where it just 'clicks.' For me, it was today.&lt;br&gt;
I’ve spent the last week moving away from manual configuration to a fully automated, role-based infrastructure. Instead of one long list of tasks, I’ve organized my environment into logical groups:&lt;br&gt;
Web Tier: Automated Apache + PHP setup (handling Ubuntu/CentOS differences).&lt;br&gt;
Database Tier: MariaDB provisioning.&lt;br&gt;
File Tier: Samba deployment using the agnostic package module.&lt;/p&gt;

&lt;p&gt;The best part? It’s completely smart. Using Ansible facts, the playbook detects the OS and adjusts package names and managers (apt vs. dnf) on the fly. No more 'permission denied' errors or broken dependencies—just clean, idempotent automation.&lt;/p&gt;

&lt;p&gt;Key Lesson Learned: Formatting is everything in YAML! Managing multiple 'plays' in one file requires strict attention to indentation and host grouping.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fut27a93xckw5twqqfp32.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fut27a93xckw5twqqfp32.jpg" alt=" " width="710" height="919"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5cliwb3j99wcqpc5bboj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5cliwb3j99wcqpc5bboj.jpg" alt=" " width="580" height="637"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ansible</category>
      <category>ubuntu</category>
      <category>devops</category>
      <category>automation</category>
    </item>
    <item>
      <title>Smart Playbooks: Handling Ubuntu and CentOS in one go with Ansible</title>
      <dc:creator>Femi</dc:creator>
      <pubDate>Sat, 11 Apr 2026 21:57:55 +0000</pubDate>
      <link>https://forem.com/oofemi/smart-playbooks-handling-ubuntu-and-centos-in-one-go-with-ansible-3jo4</link>
      <guid>https://forem.com/oofemi/smart-playbooks-handling-ubuntu-and-centos-in-one-go-with-ansible-3jo4</guid>
      <description>&lt;p&gt;One of the biggest hurdles in automation is environmental drift—specifically when your fleet isn't running the same OS.&lt;br&gt;
​I recently tackled this in an enterprise environment. I wanted to deploy a web stack, but my nodes are a mix of Ubuntu 24.04 and CentOS. Since Ubuntu uses apt and Apache is called apache2, while CentOS uses dnf and Apache is called httpd, a simple script wouldn't cut it.&lt;/p&gt;

&lt;p&gt;​Enter Ansible Conditionals.&lt;br&gt;
​By using the when statement tied to the ansible_distribution fact, I built a 'smart' playbook that:&lt;br&gt;
​Detects the OS automatically.&lt;br&gt;
​Runs apt tasks for Ubuntu and dnf for CentOS.&lt;br&gt;
​Installs the correct package names for each.&lt;/p&gt;

&lt;p&gt;​It’s a small logic jump, but it’s the difference between a playbook that works on one machine and a playbook that works on a thousand.&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%2Fe1rh081g3x0xddnjlyhb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe1rh081g3x0xddnjlyhb.jpg" alt=" " width="800" height="1319"&gt;&lt;/a&gt;&lt;br&gt;
​Next up on my journey: Implementing Handlers to make these updates even more efficient! 🚀&lt;/p&gt;

</description>
      <category>devops</category>
      <category>automation</category>
      <category>linux</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>The proof-of- work with Ansible</title>
      <dc:creator>Femi</dc:creator>
      <pubDate>Fri, 10 Apr 2026 21:11:19 +0000</pubDate>
      <link>https://forem.com/oofemi/the-proof-of-work-with-ansible-2jm1</link>
      <guid>https://forem.com/oofemi/the-proof-of-work-with-ansible-2jm1</guid>
      <description>&lt;p&gt;Seeing "changed=1" is the most satisfying feeling in DevOps. 🚀&lt;br&gt;
​I just finished building a playbook that handles the full onboarding of a new admin user across multiple Ubuntu servers simultaneously.&lt;/p&gt;

&lt;p&gt;​The stack:&lt;br&gt;
​Control Node: Ubuntu 24.04 (Brown)&lt;br&gt;
​Managed Nodes: 3x Ubuntu VMs&lt;br&gt;
​Orchestration: Ansible&lt;br&gt;
​Security: SHA-512 Hashing &amp;amp; SSH Key injection&lt;/p&gt;

&lt;p&gt;​By moving away from manual useradd commands, I’m ensuring that my infrastructure is consistent, secure, and easily reproducible. Every error today was just a lesson in how the Linux shadow file works under the hood.&lt;br&gt;
​Moving one step closer to a full automation  🐧💻&lt;/p&gt;

&lt;p&gt;​#Ansible #DevOps #SysAdmin #Tech&lt;br&gt;
 #Infrastructure&lt;/p&gt;

</description>
      <category>devops</category>
      <category>ansible</category>
      <category>automation</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
