<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
    <channel>
        <title>SysAdmin Journal</title>
        <link>https://sysadmin-journal.com</link>
        <description>Thoughts, ideas and stories</description>
        <language>en</language>
        <lastBuildDate>Sun, 06 Sep 2026 09:06:00 +0000</lastBuildDate>
        <atom:link href="https://sysadmin-journal.com/rss" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        <item>
            <title>Sovereign by Design: Notes from the Panel on Africa&#039;s Data, Cloud and AI Future</title>
            <link>https://sysadmin-journal.com/sovereign-by-design-notes-from-the-panel-on-africas-data-cloud-and-ai-future</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/sovereign-by-design-notes-from-the-panel-on-africas-data-cloud-and-ai-future</guid>
            <pubDate>Sun, 06 Sep 2026 09:06:00 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>MSCC</category>
            <description>This year I picked the title &quot;Sovereign by Design&quot; for the panel discussion at the Developers Conference. Four panelists spent an hour and a half on one question. What does it actually take for Africa to own its digital future? Their answers covered local talent, submarine cables and GPUs, the African languages missing from AI models, and the supply chain links we forget to check.</description>
            <media:content url="https://sysadmin-journal.com/content/images/2026/09/sovereign-by-design-devcon2026.jpg" medium="image" />
            <content:encoded><![CDATA[<p>I started the panel discussion at the Developers Conference a few years ago, and I organise and moderate it every year. In previous editions we talked about Cloud-Native and about Cybersecurity. This year I picked the title &quot;Sovereign by Design&quot;.</p>
<p>The title was a deliberate choice. Sovereignty is not something you declare in a strategy document. It is the result of many smaller decisions. What you build and what you buy. Whose infrastructure you depend on. Who you hire. What you leave unchecked.</p>
<p>We took the stage on the second morning of the conference, right after the keynote by the CEO of Mauritius Telecom. Over the next hour and a half, four panelists worked through one question. What does it actually take for Africa to own its digital future? What follows is not a transcript. These are the arguments that stayed with me.</p>
<h2>Anousha Sathan on local talent</h2>
<p>Anousha Sathan, General Manager of Currimjee Informatics Ltd, said the sovereignty conversation is incomplete without skilled local talent. You can bring your data home and build local data centres, and still depend on someone else, because the people who design and run those systems sit abroad.</p>
<figure><img src="https://sysadmin-journal.com/content/images/2026/09/anousha-sathan-general-manager-currimjee-informatics.jpeg" alt="Anousha Sathan, General Manager of Currimjee Informatics Ltd" /><figcaption>Anousha Sathan, General Manager of Currimjee Informatics Ltd | Photo by Arwin Neil Baichoo</figcaption></figure>
<p>She was frank about the situation in Mauritius. Big projects often go to foreign companies, and the reason given is usually the same. There is not enough local expertise. She asked the room to see this for what it is, a vicious circle. If local talent never gets a fair chance on serious work, it never builds the track record that would win the next project, and the shortage keeps repeating itself. Breaking the circle means being willing to bet on local capability, even when the safer choice points overseas. For her, sovereignty starts with how we hire and how we award contracts.</p>
<h2>Naveesh Doolhur on the hardware underneath</h2>
<p>The conversation then moved to the physical layer, the cables, data centres and compute that everything else sits on. Naveesh Doolhur, Head of AI &amp; Data Analytics at Mauritius Telecom, did not dispute the starting point. Much of the submarine fibre in the region is owned by international companies and consortia.</p>
<figure><img src="https://sysadmin-journal.com/content/images/2026/09/naveesh-doolhur-sovereign-by-design-panelist.jpeg" alt="Naveesh Doolhur, Head of AI &amp; Data Analytics at Mauritius Telecom" /><figcaption>Naveesh Doolhur, Head of AI &amp; Data Analytics at Mauritius Telecom | Photo by Arwin Neil Baichoo</figcaption></figure>
<p>But he did not accept that nothing can change. Local ISPs have been investing, and Mauritius Telecom in particular. He pointed to MT's investment in top-tier data centre capabilities, and to its purchase of GPUs, the expensive hardware that any serious AI work now depends on. The aim is practical. Local players should have somewhere to build and host their platforms, and now to train and run models, without automatically going to a foreign hyperscaler. His point was that the work is already funded and under way.</p>
<h2>Dylan Harbour on the gap in the models</h2>
<p>Dylan Harbour, Director of Technology at Ringier South Africa, spoke about how AI is changing the media industry, especially the way content is created. The tools are moving fast, and newsrooms across the continent already feel the shift.</p>
<figure><img src="https://sysadmin-journal.com/content/images/2026/09/dylan-harbour-devcon2026.jpeg" alt="Dylan Harbour, Director of Technology at Ringier South Africa" /><figcaption>Dylan Harbour, Director of Technology at Ringier South Africa | Photo by Paul Dylan</figcaption></figure>
<p>His most interesting point was about what is missing from the models, and about who holds the material to fill that gap.</p>
<blockquote>
<p>It's an opening, if you're careful. The big models were trained where the payoff was biggest, so smaller languages and local context got left out. Media companies here sit on decades of content in exactly those languages and with local context, and a lot of it has never been public. The harder question is what shape the offering takes next, because whatever we build has to keep transparency and trust intact. — Dylan Harbour</p>
</blockquote>
<p>African languages are missing from the big models because the money was elsewhere. The training data followed the return, and our languages were not where the return was. That gap is not permanent though. African publishers hold archives that no web crawler has ever seen, decades of reporting in local languages, much of it never published online. In a market where good data is the constraint, that is a rare asset.</p>
<p>His warning matters as much as the opportunity. Deciding what to do with those archives is the hard part, whether that is licensing, a partnership, a product or a shared corpus. And it has to be done without losing the transparency and trust that make a media house worth reading in the first place. Owning the data is only half of it. The other half is being careful about how you use it.</p>
<h2>Danté Sassenberg on supply chain risk</h2>
<p>Danté Sassenberg, Head of Pentesting at Integrity360, spoke about a threat that rarely makes the news but does a lot of damage, the supply chain. His message was simple. No weak link should be left unchecked.</p>
<figure><img src="https://sysadmin-journal.com/content/images/2026/09/dante-sassenberg-devcon2026.jpeg" alt="Danté Sassenberg, Head of Pentesting at Integrity360, South Africa" /><figcaption>Danté Sassenberg, Head of Pentesting at Integrity360, South Africa | Photo by Arwin Neil Baichoo</figcaption></figure>
<p>Two things make this urgent. First, AI has made attacker tools faster and cheaper than they were a year ago. Second, companies relax because they work with large, well-known vendors. That comfort is understandable, but it hides the real exposure. A third-party dependency, a library, a plugin or a supplier's integration quietly becomes the softest point in a system that looks well protected. Trusting a big name at the front door counts for little if the breach comes through a forgotten link further down the chain. For him, sovereignty includes knowing your dependencies well enough to defend them.</p>
<h2>Conclusion</h2>
<p>Four panelists, four points of view, and one common thread. Talent, infrastructure, data and dependencies are not competing definitions of sovereignty. They are the walls that hold it up. Take one away and the structure leans.</p>
<p>If there was a single lesson, it was this. Sovereignty is not a status you announce. It is a set of decisions you make, again and again, and usually against the easier option. The safe procurement choice, the foreign hyperscaler, the trusted vendor, the well-resourced language. Each one is the default, and each default gives away a little ground. Building differently is harder. That is what doing it by design actually means.</p>
<p>My thanks to Anousha, Dylan, Danté and Naveesh for a solid hour and a half of discussion, and to everyone who stayed in the room to be part of it.</p>
]]></content:encoded>
        </item>
        <item>
            <title>Essential Linux Skills for Google Cloud Platform — DevCon 2026</title>
            <link>https://sysadmin-journal.com/essential-linux-skills-for-google-cloud-platform-devcon-2026</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/essential-linux-skills-for-google-cloud-platform-devcon-2026</guid>
            <pubDate>Sun, 02 Aug 2026 08:57:00 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>MSCC</category>
            <category>Conference</category>
            <category>Google Cloud Platform</category>
            <description>My first talk as a Google Developer Expert in Cloud: the Linux skills that matter most on GCP, delivered to a packed room at the Developers Conference 2026 in Mauritius.</description>
            <media:content url="https://sysadmin-journal.com/content/images/2026/07/ish-sookun-developers-conference-2026-essential-linux-for-google-cloud-platform.jpeg" medium="image" />
            <content:encoded><![CDATA[<p>On Thursday, 23 July 2026, at 1 p.m., I walked into the Educator 1 room at the
Voilà Hotel in Bagatelle to deliver a talk I had been looking forward to for
weeks: <strong>Essential Linux Skills for Google Cloud Platform</strong>, at the Developers
Conference 2026.</p>
<p>It was a first for me in more ways than one. Six days earlier, on Friday 17 July,
I received the email confirming my acceptance as a <strong>Google Developer Expert in Google Cloud — Modern and Enterprise Infrastructure</strong>.</p>
<figure class="mt-10 max-w-4xl mx-auto">
<img src="https://sysadmin-journal.com/content/images/2026/07/developers-conference-2026-new-gdes-in-mauritius.jpeg" />
<figcaption class="-mt-10 text-center text-xs font-mono text-ink-500 dark:text-ink-400">JoKi on the left, with Noor (right) and myself (center) — the two recent GDEs from Mauritius</figcaption>
</figure>
<p>DevCon 2026 was my first GCP talk wearing that hat, and I will admit the timing made the session feel special. <em>The photo was taken on Day 3, since the new GDE announcement was made during the Google keynote.</em></p>
<h2>A packed room, and a lot of new faces</h2>
<p>The room filled up quickly — with a young audience, many of whom I saw for the first time. After years in the local community — MSCC, DevCon, DevFest, openSUSE, Cloud Native, meetups — you start to recognise everyone.</p>
<p><img src="https://sysadmin-journal.com/content/images/2026/07/developers-conference-2026-essential-linux-for-google-cloud-platform-attendees.jpeg" alt="" /></p>
<p>Thursday afternoon was the opposite of that, and it was genuinely encouraging. There is a new generation coming up that wants to do infrastructure work, and they showed up at 1 p.m. on a working day to learn Linux.</p>
<h2>The premise: the cloud didn't replace Linux — it runs on it</h2>
<p>That was the line on my title slide, and the whole talk hangs on it. Roughly 90%
of public-cloud workloads run on Linux. Every GKE node boots a Linux kernel.
Every container on Cloud Run or GKE is a Linux process in namespaces and cgroups.
Abstractions leak, and when a deployment fails at 2 a.m., the fix is almost never
a button in the console — it's a Linux skill.</p>
<p>So rather than a deep-dive on any one tool, I gave the room a <strong>skills map</strong>:</p>
<ul>
<li><strong>Level 1 — VM operations:</strong> SSH and key management, packages
(<code>apt</code> / <code>zypper</code> / <code>dnf</code>), files, permissions, <code>sudo</code>, disks and mounts.</li>
<li><strong>Level 2 — Services and network:</strong> systemd units, firewalls at both layers,
sockets with <code>ss</code>, logs with <code>journalctl</code>.</li>
<li><strong>Level 3 — Containers and GKE:</strong> containers as Linux processes, images as
packaged Linux. Your VM skills transfer; GKE stops being magic.</li>
</ul>
<h2>Six demos, under two minutes each</h2>
<p>I kept the talk hands-on with six short live demos on <code>africa-south1</code>:</p>
<ol>
<li><strong>Compute Engine</strong> — create a VM and get a shell entirely from <code>gcloud</code>,
with image families so you never chase image names. Ubuntu 24.04 for the demo,
with a nod to <code>--image-family=opensuse-leap</code> for those who know better. 😄
Rather than let the command scroll past, I read it out in plain English —
create an instance, in this zone, of this machine type, from this image
family, published by this project — so nobody had to guess what a flag did.
Being able to read a command and translate it into the actions it performs is
an acquired skill, and a valuable one. It comes with time and repetition.</li>
<li><strong>OS Login</strong> — IAM as your new <code>/etc/passwd</code>. I took a short detour first
through how SSH keys actually work: what the public key does, what the private
key never does. Then a light touch on PAM and the Google Cloud guest agent,
which is what allows <code>gcloud</code> to handle authentication instead of leaning on a
local Unix account. Then the proof — I logged into the instance from the first
demo with no Unix account on the system, no SSH key on it and no password. No
key sprawl, instant revocation, every login in Cloud Audit Logs.
<code>roles/compute.osLogin</code> for a shell, <code>osAdminLogin</code> for sudo.</li>
<li><strong>systemd and journalctl</strong> — the plan was to install nginx, break it and
triage it live, but the clock said otherwise. So I stayed on the instance we
already had and read the OpenSSH logs and the PAM entries written the moment I
connected through OS Login — the same authentication path we had just talked
about, now visible in the journal. The three filters that solve most
incidents: <code>-u</code> for one unit, <code>-p err</code> for errors only, <code>--since</code> to bound the
window.</li>
<li><strong>Networking</strong> — two firewalls, one mental model. I covered <code>ufw</code> and
<code>firewalld</code> and the job each one does, then made the case for filtering a
layer higher. If you handle everything at the instance, a heavy traffic attack
still reaches your VM and your CPU peaks filtering out the nonsense. Push it
to the Cloud Firewall and GCP absorbs that pressure before it ever touches
you — and the rules stay one command away. The heuristic that saves hours: a
<strong>timeout</strong> usually means the VPC firewall dropped your packets, a
<strong>connection refused</strong> means you reached the host and nothing was listening.
<code>ss -tulpn</code> settles the argument.</li>
<li><strong>Storage</strong> — grow a disk with zero downtime: resize the Persistent Disk,
then <code>growpart</code>, then <code>resize2fs</code> (or <code>xfs_growfs</code>). Best wow-per-second of
the session.</li>
<li><strong>Observability</strong> — install the Ops Agent and watch journald entries land in
Cloud Logging, then query the whole fleet with <code>gcloud logging read</code>. Your
Linux logging knowledge doesn't get replaced in the cloud, it gets multiplied.</li>
</ol>
<p>A few things needed no demo but earn their place in the muscle-memory layer:
<code>chmod</code> / <code>chown</code> / <code>umask</code> (where most &quot;the app can't write&quot; bugs live), sudoers
drop-ins instead of editing <code>/etc/sudoers</code>, <code>grep -r</code> and <code>tail -f</code> for text
triage, and systemd timers over cron — logged, dependency-aware, testable.</p>
<h2>For those coming from Windows</h2>
<p>Instead of arguing the case, I opened <a href="https://top500.org">top500.org</a> live and
generated a list of the world's 500 fastest supercomputers with the operating
system and operating system family columns showing. The most recent benchmark had
been published in June 2026, only weeks before the talk. No surprise to anyone
who follows this list: all 500 of them run a variant of Linux. Five hundred out
of five hundred.</p>
<p>It makes the point better than I could. The machines at the very top of the field
and the VM we had spun up ten minutes earlier on Compute Engine speak the same
language.</p>
<h2>Homework</h2>
<p>I closed with a ten-item checklist, one skill per evening on the free tier: from
&quot;create and SSH into a VM entirely from <code>gcloud</code>&quot; to &quot;explain why a container is
just a Linux process.&quot; Tick all ten and you're production-ready on GCP —
everything else is depth, not breadth.</p>
<p>The Q&amp;A ran the full ten minutes, with the expected and very welcome question
about which distributions we run in production and why.</p>
<p>Thank you to everyone who came, and to the DevCon team for another well-run
edition. The slides are on their way here, and the small workaround needed to get
the Ops Agent repo script happy on openSUSE Leap 16 deserves a post of its own.</p>
<blockquote>
<p>The cloud is someone else's computer. It still speaks Linux.</p>
</blockquote>
]]></content:encoded>
        </item>
        <item>
            <title>A Day With openSUSE at Polytechnics Mauritius</title>
            <link>https://sysadmin-journal.com/a-day-with-opensuse-at-polytechnics-mauritius</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/a-day-with-opensuse-at-polytechnics-mauritius</guid>
            <pubDate>Sun, 24 May 2026 13:55:00 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>openSUSE</category>
            <description>A full-day open source workshop at the Polytechnics Mauritius campus in Ébène — tracing Linux from Stallman and Minix to a Mauritian gecko on the openSUSE Leap 16.0 wallpaper, packaging FrankenPHP live on the Open Build Service, and spinning up openSUSE on Google Cloud. With Eddy Lareine and Neil Baichoo.</description>
            <media:content url="https://sysadmin-journal.com/content/images/2026/05/opensuse-workshop-polytechnics-mauritius.jpeg" medium="image" />
            <content:encoded><![CDATA[<p>On Wednesday 13 May 2026, I spent the full day at the <strong>Polytechnics Mauritius</strong> campus in <strong>Ébène</strong>, running a workshop on open source software with openSUSE as the running thread. Morning and afternoon sessions, two distinct flavours, one community story. The day was organised and facilitated by <strong>Nishtee Gopee</strong>, Programme Leader of IT &amp; Emerging Technologies at Polytechnics Mauritius, together with lecturers <strong>Arun Goorsaha</strong> and <strong>Salim Soobadar</strong> — who kept the energy in the room high and gently nudged every student to participate fully, ask questions, and stay for the demos. I was joined as co-presenter by two openSUSE friends from the local scene: <strong>Eddy Lareine</strong> and <strong>Neil Baichoo</strong>.</p>
<p><img src="https://sysadmin-journal.com/content/images/2026/05/ish-sookun-introducing-opensuse-polytechnics-mauritius.jpeg" alt="Contributing to openSUSE at Polytechnics Mauritius" /></p>
<h2>The Morning: How We Got Here</h2>
<p>I opened with history, not commands. You cannot really appreciate why openSUSE looks and behaves the way it does without first understanding the soil it grew from.</p>
<p>So we went back to <strong>Richard M. Stallman</strong>, the GNU Project, and the four freedoms. Then to the <strong>BSD lawsuit</strong> that bogged the alternative UNIX world down for years, and to a Finnish student named <strong>Linus Torvalds</strong> who, while studying operating systems under Professor <strong>Andrew Tanenbaum</strong> and his teaching OS <strong>Minix</strong>, decided in 1991 to write his own kernel — &quot;just a hobby, won't be big and professional like GNU.&quot;</p>
<p><img src="https://sysadmin-journal.com/content/images/2026/05/linux-beginning.jpeg" alt="Pointing at Linus Torvalds' famous 1991 Usenet post" /></p>
<p><em>&quot;Just a hobby.&quot; — The most consequential understatement in computing history.</em></p>
<p>From there it was a natural step into <strong>software licences</strong> — copyleft (GPL family), permissive (MIT, BSD, Apache), and the practical difference for someone who wants to use, modify, or ship code. We covered why the licence on a piece of code is not a detail you skim past, especially if you intend to build a product on top of it.</p>
<p>Then I shifted to <strong>openSUSE itself</strong> — thirty-four years on the same continent of Linux. Four German students starting S.u.S.E. in Nuremberg in 1992. YaST being born in 1994. Novell acquiring SUSE in 2003 for USD 210 million. The openSUSE.org project launching in 2005. EQT buying SUSE in 2018 for USD 2.5 billion. SUSE going public on the Frankfurt stock exchange in 2021. Going private again in 2023. And through all of that corporate motion, the openSUSE Project remaining independent, <strong>Board-elected, community-led</strong>.</p>
<p><img src="https://sysadmin-journal.com/content/images/2026/05/34-years-of-suse.jpeg" alt="Presenting the openSUSE timeline at Polytechnics Mauritius" /></p>
<h2>You Are Already Using Open Source</h2>
<p>One slide I always enjoy showing is the <em>&quot;you are already using it&quot;</em> slide — the one that ends the imaginary debate about whether open source is mainstream. Android is Linux. Your iPhone has open source at its core. Netflix and Spotify run on it. TLS, OpenSSL and SSH guard every padlock icon you see. The Top500 supercomputers list is now <strong>100 % Linux</strong>. Every meaningful AI model and framework — PyTorch, TensorFlow, JAX, vLLM — is open source. Firefox, GCC, LibreOffice, Inkscape, Blender. The audience went quiet for a moment. Good.</p>
<p><img src="https://sysadmin-journal.com/content/images/2026/05/ish-sookun-opensuse-mauritius.jpeg" alt="" /></p>
<h2>Contributing Without Writing Code</h2>
<p>The biggest myth I wanted to dismantle that morning is the one that says you must be a kernel guru to contribute to open source. You don't. I walked the students through the non-coding tracks:</p>
<ul>
<li><strong>Wiki and documentation</strong> — fixing typos, improving install guides, translating pages.</li>
<li><strong>Artwork and marketing</strong> — designing posters, social cards, release wallpapers.</li>
<li><strong>Advocacy</strong> — speaking at universities (like the one we were standing in), running booths at conferences, evangelising at meetups.</li>
<li><strong>Helping new users</strong> — answering questions on forums, on Matrix, on Discord, on the mailing lists. Mentoring someone through their first install is a contribution.</li>
<li><strong>Bug triage and testing</strong> — running pre-release images, filing reproducible bugs.</li>
</ul>
<p>Most people who eventually become code contributors started on one of these tracks. That is worth saying out loud in a room of nineteen-year-olds who think the door is closed because they don't yet feel &quot;technical enough&quot;.</p>
<h2>Eddy on the openSUSE Board and Elections</h2>
<p><strong>Eddy Lareine</strong> then took the floor to talk about <strong>governance</strong> — the openSUSE Board, why the project has one, how elections are run, and why the rules around them matter. He explained the <strong>staggered mandate</strong> model: five board members, three-year terms, with a portion of the seats up for election each year. Continuity on one side, fresh ideas on the other. It's a design pattern I would actually borrow for any community of decent size.</p>
<p><img src="https://sysadmin-journal.com/content/images/2026/05/eddy-lareine-about-opensuse-board-elections.jpeg" alt="Eddy Lareine explaining staggered Board mandates" /></p>
<p><em>Eddy walking the students through how the openSUSE Board is structured.</em></p>
<p>For a room full of students who associate &quot;open source&quot; only with code on GitHub, seeing governance — election rules, voter eligibility, candidate manifestos — was a small revelation. Communities are not just code. They are agreements.</p>
<h2>The Afternoon: Neil, KDE, and a Gecko on a Wallpaper</h2>
<p>After lunch, <strong>Neil Baichoo</strong> presented. Neil is a software engineer who  programmed in Rust for a specialised database company, and he is a long-time contributor to the KDE project. He opened with a broad look at open source as a whole — hitting a few well-known examples and pulling up their repositories on GitHub so the students could see code from the real world. Then he told his own contribution story in the openSUSE project.</p>
<p><img src="https://sysadmin-journal.com/content/images/2026/05/neil-baichoo-opensuse-workshop-polytechnics-mauritius.jpeg" alt="Neil Baichoo presenting at Polytechnics Mauritius" /></p>
<p>Some time ago, Neil <a href="https://github.com/openSUSE/wallpapers/issues/18#issuecomment-2254466989" target="_blank">submitted a photograph</a> he had taken of a <strong>Mauritius day ornate gecko</strong> — the bright green endemic species you see on coconut palms across the island. That <a href="https://news.opensuse.org/2024/10/26/leap-tw-get-makeovers/" target="_blank">photograph was selected</a> and shipped as part of the default wallpaper set in <strong>openSUSE Leap 16.0</strong> while the photo credit was <a href="https://github.com/openSUSE/wallpapers/blob/master/leap16/license/license_arwinneil.txt" target="_blank">attributed to Neil Baichoo</a>.</p>
<p>The room lit up. The idea that a picture taken as a hobby could end up on Linux desktops worldwide was the most concrete demonstration of &quot;your contribution matters&quot; that any slide of mine could ever produce.</p>
<p>Neil also fielded the inevitable career question: <strong>&quot;Should I learn Go or Rust to get a head start?&quot;</strong> His honest answer — and I agree — was no. The global market for entry-level Go and Rust developers is small. In Mauritius, it is essentially zero. Learn the fundamentals well. Pick languages based on the problems you want to solve, not on hype cycles. Rust is wonderful when you have a reason to need it. PHP, Python, TypeScript, and Java will pay your bills.</p>
<h2>The Open Build Service Demo</h2>
<p>I then ran a live demo on how to <strong>package software for openSUSE</strong> using the <a href="https://build.opensuse.org">Open Build Service (OBS)</a>. OBS is itself an open source project, maintained by the openSUSE community, used to build packages for openSUSE, SUSE Linux Enterprise, Debian, Ubuntu, Fedora, RHEL, Arch and others from a single spec.</p>
<p>The walkthrough covered:</p>
<ul>
<li>The OBS web UI at <code>build.opensuse.org</code> — projects, packages, build results.</li>
<li>The <strong><code>osc</code></strong> command-line client — how to authenticate against your openSUSE account, check out a project, edit a <code>.spec</code> file, build locally with <code>osc build</code>, and submit with <code>osc commit</code>.</li>
<li>The anatomy of a <code>.spec</code> file — <code>Name</code>, <code>Version</code>, <code>Source0</code>, <code>BuildRequires</code>, <code>%prep</code>, <code>%build</code>, <code>%install</code>, <code>%files</code>.</li>
</ul>
<p>For the demo package, I picked <strong><a href="https://frankenphp.dev/">FrankenPHP</a></strong> — partly because it isn't currently in the openSUSE repositories, and partly because it deserves to be. FrankenPHP is a modern, high-performance PHP application server built on top of the <strong>Caddy</strong> web server, officially supported by the <strong>PHP Foundation</strong>, and capable of dramatically speeding up Laravel and Symfony applications via its <strong>worker mode</strong>. Packaging it as a proper RPM means a one-line <code>zypper install frankenphp</code> for every openSUSE user.</p>
<p>I showed the build locally first, then a <code>osc commit</code> against my home project on OBS. Watching the dependency graph resolve and the package compile across multiple distributions and architectures, from a single spec file, is one of those &quot;this is why I love openSUSE&quot; moments.</p>
<h2>openSUSE on Google Cloud, in Two Minutes</h2>
<p>Several students wanted to know how to <em>get started</em> with openSUSE without re-partitioning their laptops. I switched tabs to the <strong>Google Cloud Console</strong> and walked them through spinning up a free-tier <code>e2-micro</code> Compute Engine VM with openSUSE Leap 16.0.</p>
<p><img src="https://sysadmin-journal.com/content/images/2026/05/ish-sookun-opensuse-leap-google-cloud-demo.jpeg" alt="Spinning up openSUSE Leap 16.0 on Google Compute Engine" /></p>
<p>While at it, I clarified a question that comes up surprisingly often: the difference between <strong>SUSE Linux Enterprise Server (SLES)</strong> and <strong>openSUSE Leap</strong> on GCP.</p>
<p><img src="https://sysadmin-journal.com/content/images/2026/05/suse-vs-opensuse.jpeg" alt="Explaining on Linux distributions on Google Cloud Platform" /></p>
<ul>
<li><strong>SLES</strong> is the commercial product. The image carries a per-hour licence fee on top of the compute cost, and it ships with SUSA support entitlements. It is what you choose when you are running SAP HANA in production and you want a phone number to call at 3 a.m.</li>
<li><strong>openSUSE Leap</strong> is the community distribution, built from the same SLE codebase. The image is <strong>free of licence cost</strong> — you pay only for the compute and storage. It is what you choose to learn, to develop, to host your blog, or to build RPMs.</li>
</ul>
<p>For a student on a free trial, openSUSE Leap on <code>e2-micro</code> in <code>us-central1</code> (one of the free-tier regions) is the right starting point.</p>
<p><img src="https://sysadmin-journal.com/content/images/2026/05/ish-sookun-opensuse-leap-console-google-cloud.jpeg" alt="Terminal console accessing openSUSE Leap 16.0 on GCP" /></p>
<h2>What Didn't Quite Go to Plan</h2>
<p>Several students were keen to install openSUSE Leap 16.0 locally in <strong>VirtualBox</strong>. In theory: download the ISO, boot the VM, install. In practice, on a Wednesday at Polytechnics, three things went wrong:</p>
<ol>
<li>The campus Internet connection wasn't generous. Half the ISO downloads finished with <strong>corrupt images</strong> that failed checksum verification (or worse, booted into installer errors).</li>
<li>Several laptops simply did not have enough free RAM or disk to give a VM a comfortable 4 GB / 20 GB allocation.</li>
<li>The few that did succeed spent fifteen minutes in installer screens instead of actually <em>using</em> openSUSE.</li>
</ol>
<p>This is the part of the day I will fix for next time.</p>
<h2>Lessons Learned — A Browser-Based openSUSE Playground</h2>
<p>The takeaway from the afternoon is clear: <strong>for a workshop setting, full local installation is the wrong shape of the problem</strong>.</p>
<p>What I want to build before the next session is a small <strong>web tool</strong> that drops a student straight into a fresh <strong>openSUSE Leap container</strong> running in their browser — a terminal in a <code>&lt;div&gt;</code>, a real shell on the other end, <code>zypper</code> working, <code>osc</code> pre-installed, no download, no ISO, no checksum failures, no VirtualBox.</p>
<p>Fast, fun, and productive. That's the shape of the next openSUSE workshop in Mauritius.</p>
<h2>Closing Thoughts</h2>
<p>A whole day workshop is a long time on your feet, but the energy in the room kept me going. Students who came in thinking <em>&quot;Linux is what servers run&quot;</em> left understanding that Linux is what <strong>everything</strong> runs — and that they have a seat at the table from day one, whether they write code, write documentation, take photographs of geckos, or simply show up to help someone else install their first distro.</p>
<p>A big thanks to <strong>Polytechnics Mauritius</strong> for hosting us at the Ébène campus, to <strong>Nishtee Gopee</strong>, <strong>Arun Goorsaha</strong> and <strong>Salim Soobadar</strong> for organising the day and bringing such an engaged cohort into the room, to <strong>Eddy</strong> and <strong>Neil</strong> for joining as co-presenters, and to every student who stayed past 4 p.m. to ask &quot;one more question&quot; at the front of the room.</p>
]]></content:encoded>
        </item>
        <item>
            <title>Document AI &amp; RAG for a Newspaper Archive</title>
            <link>https://sysadmin-journal.com/document-ai-rag-for-a-newspaper-archive</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/document-ai-rag-for-a-newspaper-archive</guid>
            <pubDate>Sun, 17 May 2026 09:45:52 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>Build with AI</category>
            <description>A Saturday morning of workshops and corridor conversations with Mauritius&#039;s developer community at SWAN HQ in Port-Louis. My own session was on Document AI and RAG for a historical newspaper archive — turning a 1955 Le Figaro into something a researcher can actually query.</description>
            <media:content url="https://sysadmin-journal.com/content/images/2026/05/build-with-ai-2026-opening.jpeg" medium="image" />
            <content:encoded><![CDATA[<p>On Saturday May 9 2026, I participated in the <a href="https://gdg.community.dev/events/details/google-gdg-mauritius-presents-build-with-ai-2026" target="_blank">Google Build with AI</a> event organised by GDG Mauritius and hosted by SWAN, at their Port-Louis Head-Office. 36 people attended the event. My presentation was titled &quot;Document AI &amp; RAG for a Newspaper Archive.&quot; I started the talk with a soft &quot;Hello&quot; and pulled up the Google Chrome browser on the screen with a tab showing Gemini. It had a question, &quot;What did Le Figaro write about Bao Dai in 1955?&quot;</p>
<blockquote>
<p><em>There's no precise search result for 'What did Le Figaro write about Bao Đại in 1955?'. Let's broaden the search to look at French newspapers covering Bao Dai in late 1955, particularly around the October 1955 referendum where Ngo Dinh Diem deposed him.</em></p>
</blockquote>
<div>
<img src="https://sysadmin-journal.com/content/images/2026/05/gemini-answering-question-about-le-figaro.jpg" alt="Gemini answering the question about Le Figaro and Bao Dai." />
</div>
<p>I let that sink in for a moment. Then I switched to my terminal and ran a small Python script — same question, against a tool I had built and trained on actual 1955 issues of Le Figaro. The answer came back in about four seconds. Concise. Specific. Footnoted, with each citation linking back to the precise page in the original PDF where the claim came from.</p>
<div>
<img src="https://sysadmin-journal.com/content/images/2026/05/build-with-ai-ish-sookun-running-script.jpeg" alt="Running the query using a Python script." />
</div>
<p>Different tool. Different answer. Same question.</p>
<p>That contrast was the whole reason I was doing that presentation.</p>
<h2>A quick note on framing</h2>
<p>I opened the talk by introducing myself and where I work — La Sentinelle Ltd, publisher of <em>l'express</em>, <em>5-Plus</em>, <em>Business Magazine</em>, <em>Turf Magazine</em>, and a few other Mauritian titles. And I was up front about one thing: <strong>this is a personal hobby project.</strong> Nights and weekends, no La Sentinelle mandate. If it grows into something the company or other regional publishers want to adopt one day, brilliant. For now it's just me, a laptop, and a stubborn belief that queryable newspaper archives are worth building.</p>
<h2>Why old newspapers are hard</h2>
<p>Generally-available chatbots are excellent at the surface of public knowledge. Ask Gemini about the 1955 Vietnamese referendum and it'll tell you Ngô Đình Diệm &quot;won&quot; with an implausible 98.2% of the vote. Ask it what <em>Le Figaro specifically wrote about it on a specific date, with citations</em>, and it falls over.</p>
<p>Two reasons. <strong>The training data isn't there</strong> — archives like the BnF's Gallica hold millions of 1950s pages, but as PDFs of microfilm with only basic OCR. <strong>And modern OCR doesn't help much either</strong> — tools trained on contemporary invoices and magazines aren't ready for a seven-column 1955 broadsheet. They read horizontally across columns and produce what I call <strong>column-bleed</strong>.</p>
<div>
<img src="https://sysadmin-journal.com/content/images/2026/05/Document_AI_RAG_Slide_7.jpg" alt="Anatomy of a 1955 newspaper" />
</div>
<p>A real <code>pdftotext</code> output from the 10 May 1955 Le Figaro:</p>
<blockquote>
<p><em>L'Allemagne LE TRAITÉ DANS UNE INTERVIEW Gérard Dupriez AUJOURD'HUI / a fait hier D'ÉTAT EXCLUSIVE BAO DAI parricide SEIZE PAGES.</em></p>
</blockquote>
<p>Four unrelated articles spliced together. Articles don't even live in one column — the Bao Đại interview starts in column 3 of page 1, runs into column 4, then jumps to &quot;page 13, columns 3, 4 and 5&quot; via a tiny « Suite page 13 » pointer. One article. Two pages. Five columns. Then there are the ads — about 40% of every page, much of it dressed up as editorial. Orlane sells beauty cream under the headline « À peau soignée, beau maquillage ». Pullnyl's « La révélation de l'année » reads like a scoop until you notice it's selling nylon shirts at 1,200 francs. If your pipeline can't tell these from editorial, your knowledge base ships ad copy as facts.</p>
<h2>The pipeline</h2>
<p>Five stages, end to end:</p>
<ol>
<li><strong>Ingest</strong> — PDFs land in Cloud Storage.</li>
<li><strong>Parse</strong> — Document AI Layout Parser. Hierarchical block structure, cross-column reading order.</li>
<li><strong>Enrich</strong> — Gemini 2.5 multimodal. Article assembly, kind classification, jump resolution. <em>This is where the actual contribution lives.</em></li>
<li><strong>Index</strong> — Cloud SQL Postgres 18 with pgvector.</li>
<li><strong>Generate</strong> — Gemini grounded answers with inline citations.</li>
</ol>
<div>
<img src="https://sysadmin-journal.com/content/images/2026/05/build-with-ai-ish-sookun-explaining-pipeline.jpeg" alt="Explaining the five stages of the pipeline" />
</div>
<p>Stages 1, 4 and 5 are well-trodden. Stages 2 and 3 are where teams fall down on newspaper data.</p>
<h2>A few interesting moments from the build</h2>
<p>Most of the provisioning is the kind of <code>gcloud</code> you've seen a hundred times. Three things stood out as worth showing.</p>
<p><strong>Cloud SQL Postgres 18 with pgvector.</strong> Postgres 18 went GA on Cloud SQL this year, and <code>pgvector</code> is now a first-class extension:</p>
<pre><code class="language-bash">gcloud sql instances create figaro-db \
  --database-version=POSTGRES_18 \
  --tier=db-custom-1-3840 --region=us-central1 \
  --root-password=&quot;$(openssl rand -base64 24)&quot;
</code></pre>
<p>The actual technical contribution sits in the schema — a French <code>tsvector</code> column and a 768-dimensional embedding column in the same row, each with the right index:</p>
<pre><code class="language-sql">CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE articles (
  -- ... id, source, page, published, title, body ...
  kind      TEXT CHECK (kind IN
              ('news','feature','opinion','advertorial','ad','listing')),
  tsv       TSVECTOR GENERATED ALWAYS AS (
              setweight(to_tsvector('french', coalesce(title,'')), 'A') ||
              setweight(to_tsvector('french', coalesce(body,'')),  'B')
            ) STORED,
  embedding VECTOR(768)
);

CREATE INDEX ON articles USING GIN (tsv);
CREATE INDEX ON articles USING hnsw (embedding vector_cosine_ops);
</code></pre>
<p>The whole retrieval story rests on those two columns existing side-by-side, each with the appropriate index. GIN for full-text, HNSW for cosine similarity.</p>
<p><strong>Document AI has no <code>gcloud</code> surface.</strong> This is the kind of friction nobody warns you about. Want to create a Layout Parser processor? <code>gcloud documentai processors create</code> does not exist — not in <code>gcloud</code>, not in <code>gcloud alpha</code>, not anywhere. You hit the REST API directly:</p>
<pre><code class="language-bash">curl -X POST \
  -H &quot;Authorization: Bearer $(gcloud auth print-access-token)&quot; \
  -H &quot;Content-Type: application/json&quot; \
  -d '{&quot;displayName&quot;:&quot;figaro-layout-parser&quot;,&quot;type&quot;:&quot;LAYOUT_PARSER_PROCESSOR&quot;}' \
  &quot;https://us-documentai.googleapis.com/v1/projects/$PROJECT_ID/locations/us/processors&quot;
</code></pre>
<p>Two quirks worth knowing. The endpoint is <code>us-documentai.googleapis.com</code> (multi-region) — Document AI doesn't expose per-region endpoints like <code>us-central1</code>. And the <code>locations/us</code> in the path matches that.</p>
<h2>Passing two modalities to Gemini</h2>
<p>For each page, the ingest splits the source PDF to a single page (Document AI's sync endpoint caps at 40 MB), parses the layout, renders the page as JPEG, and hands Gemini both inputs together:</p>
<pre><code class="language-python">def extract_articles(layout_chunks, page_image, page_num):
    prompt = f&quot;&quot;&quot;Group blocks into articles from this 1955 French newspaper.
Use the page image as authoritative for grouping. For each article return:
title, body, byline, kind (news|feature|opinion|advertorial|ad|listing),
and jump_target if a « Suite page X » pointer is visible. Translate nothing.

Layout chunks for page {page_num}: {json.dumps(layout_chunks)[:12000]}&quot;&quot;&quot;

    response = gen.models.generate_content(
        model=&quot;gemini-2.5-flash&quot;,
        contents=[
            types.Part.from_bytes(data=page_image, mime_type=&quot;image/jpeg&quot;),
            prompt,
        ],
        config=types.GenerateContentConfig(
            response_mime_type=&quot;application/json&quot;,
            temperature=0.2,
        ),
    )
    return json.loads(response.text)
</code></pre>
<p>The image gives Gemini visual context the JSON can't carry — ad framing, advertorial styling, « Suite page X » pointers, the way a section break looks different from a paragraph break. The model literally <em>sees</em> what a 1955 newspaper reader saw. The <code>kind</code> value in the output is the linchpin: classifying ads vs editorial at ingest time keeps ad copy out of every future retrieval, forever.</p>
<h2>Retrieval is hybrid by necessity</h2>
<p>The query that runs against the database has two CTEs side by side:</p>
<pre><code class="language-sql">WITH lexical AS (
  SELECT id, ROW_NUMBER() OVER (
           ORDER BY ts_rank_cd(tsv, q) DESC) AS rk
  FROM articles, plainto_tsquery('french', :query) q
  WHERE tsv @@ q
    AND kind IN ('news','feature','opinion')
    AND published BETWEEN :from AND :to
  LIMIT 50
),
semantic AS (
  SELECT id, ROW_NUMBER() OVER (
           ORDER BY embedding &lt;=&gt; :qvec::vector) AS rk
  FROM articles
  WHERE kind IN ('news','feature','opinion')
    AND published BETWEEN :from AND :to
  ORDER BY embedding &lt;=&gt; :qvec::vector LIMIT 50
)
SELECT a.*,
       COALESCE(1.0/(60+l.rk), 0) + COALESCE(1.0/(60+s.rk), 0) AS score
FROM lexical l FULL OUTER JOIN semantic s USING (id)
JOIN articles a ON a.id = COALESCE(l.id, s.id)
ORDER BY score DESC LIMIT 12;
</code></pre>
<p>Three things matter here. <strong><code>tsvector</code> with <code>'french'</code></strong> gives exact-term precision — type <em>Bao Đại</em> and every chunk with that tokenisation surfaces. <strong><code>embedding &lt;=&gt; qvec</code> cosine</strong> gives paraphrase tolerance — <em>&quot;what did the South Vietnamese ruler say about elections&quot;</em> still finds chunks discussing « régime républicain ou monarchie constitutionnelle ». And <strong><code>kind IN (...)</code> on both CTEs</strong> filters Pullnyl's nylon-shirt ads out of editorial answers before either ranker sees them.</p>
<p>The fusion is <strong>Reciprocal Rank Fusion with k=60</strong> — a parameter-free way to combine rankers that produce scores on incommensurable scales. A document at rank 2 in lexical and rank 3 in semantic beats a document at rank 1 in lexical and rank 50 in semantic. The constant <code>60</code> comes from the original 2009 RRF paper and remains the empirically robust default.</p>
<p>Lexical for precision. Semantic for paraphrase. RRF to fuse them without calibrating score scales.</p>
<h2>The Mauritius angle</h2>
<p>Two hundred years of Mauritian newsprint sitting in PDFs and microfilm. <em>Le Cernéen</em> from 1832 to 1982 — the oldest paper in the southern hemisphere. <em>Le Mauricien</em> from 1908. <em>l'express</em> from 1963. Plus the National Library of Mauritius, the Mahatma Gandhi Institute archives, the official gazette since 1773. Almost none of it queryable today.</p>
<p>For institutions like the National Library or the MGI, data residency matters. The production version of this pipeline runs end-to-end in <code>africa-south1</code> — one network hop from Mauritius, historical content never leaves the region. Building this index is, in a real sense, an act of recovery: putting our history into a form our great-grandchildren can actually ask questions of.</p>
<h2>What's next</h2>
<p>A few directions on my list:</p>
<ul>
<li><strong>A knowledge graph layer</strong> — entities, dates, geographies, navigable visually.</li>
<li><strong>Multimodal photo Q&amp;A</strong> — <em>&quot;show me Cannes festival photos 1950 to 1960&quot;</em>.</li>
<li><strong>Cross-paper queries</strong> across <em>Le Figaro</em>, <em>l'express</em>, <em>Le Cernéen</em>, the gazette.</li>
<li><strong>Era-aware retrieval</strong> — 1955 vocabulary differs from 2025 vocabulary in ways that matter for embedding quality.</li>
</ul>
<p>The hobby project continues. By <strong>DevFest 2026</strong>, I'm hoping to have something more structured than a Python script firing a single query — a proper service layer, a frontend a researcher can actually navigate, maybe the cross-paper piece running across two or three titles. We'll see how far I get.</p>
<h2>Thanks</h2>
<p>Thank you to SWAN HQ for hosting a great Build with AI event in Port-Louis, to the Google Developer Group community for putting the day together, and to everyone who came up after the talk with questions, war stories, and corrections that I'm still chewing on. The slides are at the end of this post; the demo source code goes up on GitHub this week.</p>
]]></content:encoded>
        </item>
        <item>
            <title>From Flask to Cloud Run: A Workshop at ALCHE Mauritius</title>
            <link>https://sysadmin-journal.com/from-flask-to-cloud-run-a-workshop-at-alche-mauritius</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/from-flask-to-cloud-run-a-workshop-at-alche-mauritius</guid>
            <pubDate>Thu, 30 Apr 2026 09:00:51 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>Google Cloud Platform</category>
            <description>On Friday 17 April 2026, I delieverd a technical workshop at the African Leadership College of Higher Education (ALCHE) in Pamplemousses. The topic: taking a Python Flask application from a developer&#039;...</description>
            <content:encoded><![CDATA[<p>On Friday 17 April 2026, I delieverd a technical workshop at the <a href="https://alcheducation.com/">African Leadership College of Higher Education (ALCHE)</a> in Pamplemousses. The topic: taking a Python Flask application from a developer's laptop all the way to a live, publicly accessible URL on Google Cloud Run, backed by Cloud SQL for PostgreSQL. Sixteen students from the Year 2 Software Engineering cohort attended.</p>
<h2>How it came about to be?</h2>
<p>A few weeks ago, I had a conversation with Allan, Programme Officer for Software Engineering at ALCHE, about Google Cloud Platform and specifically how students could make use of the <em>Free Trial and Free Tier</em> services to deploy their school projects. He walked me through the typical stack the students work with — Python, Flask, HTML, CSS, JavaScript — the standard university curriculum. The natural next step felt obvious: let's show them what happens after the code is written.</p>
<p>I pitched the idea of a session focused on making your app accessible to people on the Internet, and Allan was on board.</p>
<p>I wasn't presenting alone. Eddy, IT Director at La Sentinelle and a certified Google Cloud Architect, joined as co-presenter. He kicked things off with an introduction to cloud computing in general before narrowing in on GCP, which was exactly the warm-up the audience needed before I dove into the more specialised services.</p>
<div class="mb-4 grid place-items-center">
    <img class="kg-image" src="https://sysadmin-journal.com/content/images/2026/04/ALCHE_Workshop_Eddy_Lareine.jpeg" alt="Eddy Lareine presenting GCP Regions & Zones at ALCHE Mauritius" />
<figcaption class="text-xs text-gray-900 -mt-8">Eddy Lareine presenting GCP Regions & Zones at ALCHE Mauritius</figcaption>
<div>
<p>His segment covered Regions &amp; Zones — a topic that trips up a lot of beginners. Not all GCP services are available in all regions, pricing varies, and critically, Free Tier services are constrained to specific regions. If you're a student in Mauritius spinning up a Cloud Run service and you pick the wrong region, you might burn through your free credits faster than expected. That context mattered.</p>
<h2>The Demo App: Petrol Watch</h2>
<p>I always prefer to demo with something tangible. The week I was planning the workshop, a petrol price hike had just been announced — so I built Petrol Watch, a small Flask application backed by PostgreSQL that tracks fuel prices published by the State Trading Corporation (STC) going back to 2004.</p>
<p>Although I'm a Laravel person at heart, I built this in Flask so the students could follow along with the stack they already know. It felt right to meet them where they are.</p>
<div class="grid place-items-center">
    <img src="https://sysadmin-journal.com/content/images/2026/04/ALCHE_Cloud_Run_Workshop_1.jpeg" alt="Presentation on Deploying Flask to Google Cloud Run" />
</div>
<h2>Containers: The Full Picture</h2>
<p>Before touching a single GCP service, I spent time on <strong>containers</strong> — what they are, where they came from, and why they matter.</p>
<p>I explained that a container image is simply your code, your runtime, your system libraries, and a minimal base OS image bundled into a single portable artefact. The same image runs on your laptop, your colleague's Mac, and Google's servers. The &quot;it works on my machine&quot; excuse disappears.</p>
<p>Then came the history lesson. Containers didn't start with Docker. I walked them through:</p>
<ul>
<li><strong>Unix chroot</strong> — the earliest form of process isolation</li>
<li><strong>BSD Jails</strong> — proper containerisation before Linux had it</li>
<li><strong>Linux Namespaces</strong> — the first namespace (<code>mount</code>) arrived in kernel 2.4.19 in August 2002; by kernel 2.6.24, the full suite of namespaces (PID, network, IPC, UTS, user) was in place, completing the isolation story</li>
<li><strong>cgroups</strong> — introduced in 2006, giving the kernel the ability to limit and account for the resources a group of processes can use; together with namespaces, this is the technical foundation everything else builds on</li>
<li><strong>Docker</strong> — popularised containers by wrapping the complexity in a usable developer experience</li>
</ul>
<p>I made a point of clarifying something that often causes confusion: <strong>Docker is a brand name, not a technology</strong>. The same instructions you put in a <code>Dockerfile</code> can equally live in a file named <strong><code>Containerfile</code></strong> — the <a href="https://github.com/containers/common/blob/main/docs/Containerfile.5.md">Containerfile specification</a> from the <code>containers/common</code> project formally defines this format, and tools like <strong>Podman</strong> and <strong>Buildah</strong> default to looking for a <code>Containerfile</code> first before falling back to <code>Dockerfile</code>. Docker, by contrast, only looks for <code>Dockerfile</code>. The syntax is identical — it's purely a naming convention, but it reflects a broader and healthier ecosystem that isn't tied to any single vendor. The industry has standardised on the Open Container Initiative (OCI) format for images, so whichever tool you use to build, you're not locked in.</p>
<p>I touched on <strong>Kubernetes</strong> as well — but deliberately kept it as a signpost to an advanced future topic. When you're deploying a single container application, Kubernetes is not the right starting point.</p>
<h2>The Deployment Spectrum</h2>
<p>One slide I always find useful is the deployment spectrum:</p>
<div class="grid place-items-center">
    <img class="-mt-8 -mb-4" src="https://sysadmin-journal.com/content/images/2026/04/Flask_to_CloudRun_ALCHE_v3_5.jpg" alt="Slide 5 showing the deployment spectrum" />
</div>
<p>Cloud Run sits in the sweet spot for most student projects and even a good chunk of production workloads: bring a container, Google handles everything else.</p>
<h2>The Dockerfile</h2>
<p>Cloud Run has exactly one requirement: a container image. Here's the Dockerfile I showed the students for Petrol Watch:</p>
<pre><code class="language-dockerfile">FROM python:3.14-slim

ENV PORT=8080 PYTHONUNBUFFERED=1

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

CMD exec gunicorn --bind :$PORT --workers 2 \
       --threads 4 --timeout 0 app:app
</code></pre>
<p>A few things worth highlighting:</p>
<ul>
<li><strong><code>python:3.14-slim</code></strong> — a smaller base image means faster cold starts and a smaller attack surface.</li>
<li><strong><code>$PORT</code></strong> — Cloud Run injects this environment variable. Never hard-code port 5000. Your container must listen on whatever <code>$PORT</code> says.</li>
<li><strong>Gunicorn</strong> — Flask's built-in development server is not for production. Gunicorn is. I took a moment to explain <em>why</em>: a web server like Apache httpd or Nginx is very good at serving static files efficiently, but it has no built-in way to execute Python code. That's where <strong>WSGI — the Web Server Gateway Interface</strong> — comes in. WSGI is a standard protocol (defined in PEP 3333) that acts as a bridge between the web server and your Python application. Gunicorn is a WSGI server: it sits between the network and Flask, receives HTTP requests, calls your application code, and returns the response. In the Cloud Run context, Gunicorn <em>is</em> the entry point — there's no separate Nginx in front of it — but the principle is the same one students will encounter whenever they deploy Python apps on a traditional server.</li>
</ul>
<p>I also explained what happens when you build the image: you get layers — files, directories, and an <strong>overlay filesystem</strong>. Each instruction in the Dockerfile adds a layer. I explained the role of <strong>container registries</strong> (Artifact Registry on GCP, Docker Hub elsewhere) as the distribution mechanism — you push your image there, and Cloud Run pulls it.</p>
<h2>Cloud SQL: Managed Postgres Without the 3 a.m. Alerts</h2>
<p>Deploying the database was almost the more interesting part of the session. I showed them how to spin up a <strong>Cloud SQL PostgreSQL</strong> instance using the <code>gcloud</code> CLI:</p>
<pre><code class="language-bash">gcloud sql instances create petrol-db \
    --database-version=POSTGRES_16 \
    --tier=db-f1-micro \
    --region=africa-south1 \
    --root-password=&quot;&lt;strong&gt;&quot;

gcloud sql databases create petrol_watch \
    --instance=petrol-db

gcloud sql users create appuser \
    --instance=petrol-db \
    --password=&quot;&lt;from Secret Manager&gt;&quot;
</code></pre>
<p>The <code>db-f1-micro</code> tier is modest — it's the &quot;tiny&quot; instance I referenced throughout the session — but it's sufficient for a demo and keeps costs near zero. Same region as your Cloud Run service means lower latency and no cross-region egress charges.</p>
<div class="grid place-items-center">
    <img src="https://sysadmin-journal.com/content/images/2026/04/ALCHE_Cloud_Run_Workshop_2.jpeg" alt="Cloud SQL on GCP" />
</div>
<h2>Wiring It All Together</h2>
<p>Deploying to Cloud Run is a single command:</p>
<pre><code class="language-bash">gcloud run deploy petrol-watch \
     --source . \
     --region=africa-south1 \
     --add-cloudsql-instances=lsl-it:africa-south1:petrol-db \
     --set-env-vars=DB_NAME=petrol_watch,DB_USER=appuser \
     --set-secrets=DB_PASSWORD=petrol-db-pass:latest \
     --allow-unauthenticated
</code></pre>
<p><code>--source .</code> tells Cloud Build to detect the Dockerfile, build the image, and push it to Artifact Registry — all automatically. <code>--add-cloudsql-instances</code> opens a secure Unix socket to Cloud SQL with no public IPs and no passwords on the wire.</p>
<h2>Sidecar Containers and One-Shot Jobs</h2>
<p>One concept I introduced was <strong>sidecar containers</strong> — the Cloud SQL Auth Proxy that Cloud Run injects alongside your app container to handle the database connection securely. Your <code>app.py</code> just connects to a local Unix socket. Google's sidecar handles the TLS and IAM authentication. You write boring, straightforward code.</p>
<p>I also explained how you can create <strong>single-use containers</strong> — Cloud Run Jobs — designed to run once and exit. The canonical use case: running database migrations and seeding data before your service goes live. Not everything needs to be a long-running HTTP server.</p>
<h2>Secrets: The One Non-Negotiable</h2>
<p>I spent a slide on this because it matters and students often get it wrong the first time:</p>
<pre><code class="language-bash"># Store once in Secret Manager
echo -n &quot;Sup3rS3cret&quot; | gcloud secrets \
    create petrol-db-pass --data-file=-

# Wire it into Cloud Run at deploy time
--set-secrets=DB_PASSWORD=petrol-db-pass:latest

# In app.py — just read an env var
os.environ['DB_PASSWORD']
</code></pre>
<p><strong>Never commit credentials to Git.</strong> Not even in a student project. Git history is permanent, and sharing your screen during a Friday demo is enough to leak a password. Secret Manager is versioned, auditable, revocable, and free for the first handful of secrets.</p>
<h2>The Questions They Asked</h2>
<p>The 2-hour session ended with a Q&amp;A, and the students' questions were genuinely good:</p>
<p><strong>Managing cloud costs</strong> came up immediately. I walked them through the Free Tier limits — 2 million Cloud Run requests per month, 360,000 GB-seconds of memory, 180,000 vCPU-seconds — and reiterated the importance of Eddy's earlier point about choosing the right region.</p>
<p><strong>Processing large datasets</strong> was the other big one. A group mentioned they had a project involving a 120 MB CSV file. I smiled. I told them the real world is considerably harsher than that — production datasets that would make a 120 MB file look like a hello world. The lesson: Cloud Run has a 60-minute request timeout, and long-running jobs belong in <strong>Cloud Run Jobs</strong> or <strong>Cloud Tasks</strong>, not a synchronous HTTP handler.</p>
<h2>Reflections</h2>
<p>Workshops like this are why I enjoy the community side of my work. These students are building with the same stack that runs production systems — Python, PostgreSQL, containers. Giving them a path from &quot;it works on my laptop&quot; to a live HTTPS URL, covered by Google's infrastructure, in under two hours, felt worthwhile.</p>
<p>If you're an educator or a developer community organiser in Mauritius and want to run something similar, reach out. I'm happy to do it again.</p>
<hr />
<p><strong>Resources mentioned during the workshop:</strong></p>
<ul>
<li><a href="https://cloud.google.com/run/docs">Cloud Run documentation</a></li>
<li><a href="https://cloud.google.com/sql/docs/postgres/connect-run">Cloud Run + Cloud SQL quickstart</a></li>
<li><a href="https://cloud.google.com/free">GCP Free Tier details</a></li>
<li><a href="https://github.com/ish-sookun/petrol-watch">Petrol Watch source code</a></li>
</ul>
<hr />
]]></content:encoded>
        </item>
        <item>
            <title>MSCC March Meetup: Enterprise Architecture &amp; 12-Factor PHP Infrastructure</title>
            <link>https://sysadmin-journal.com/mscc-march-meetup-enterprise-architecture-12-factor-php-infrastructure</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/mscc-march-meetup-enterprise-architecture-12-factor-php-infrastructure</guid>
            <pubDate>Sat, 21 Mar 2026 06:52:51 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>MSCC</category>
            <category>PHP</category>
            <description>The MSCC March meetup at Klanik, IconÉbène brought 22 attendees together for two talks: Ajmal Saifudeen on Enterprise Architecture and why developers should care, and Ish Sookun on applying the 12-Factor methodology to modern PHP infrastructure with Laravel, followed by an open discussion.</description>
            <content:encoded><![CDATA[<p>The Mauritius Software Craftsmanship Community held its monthly meetup last Saturday at the <a href="https://www.klanik.com/agences" rel="noreferrer">Klanik office</a> in IconÉbène. Twenty-two attendees gathered for an afternoon of two talks, a shared lunch, and an open discussion.</p><p>Two talks were on the agenda:</p><ol><li><strong>What is Enterprise Architecture and Why Should Developers Care?</strong> — by Ajmal Saifudeen</li><li><strong>A 12-Factor Roadmap for Modern PHP Infrastructure</strong> — by Ish Sookun (myself)</li></ol><p>Before the talks, Nirvan Pagooah, Engineering Manager at Klanik, welcomed the group and gave a brief presentation about the company, its activities, and the current open positions.</p><h2 id="enterprise-architecture-not-just-for-the-big-players">Enterprise Architecture: Not Just for the Big Players</h2><p>Ajmal Saifudeen from Enterprise Architects Ltd kicked things off with a presentation that tackled a subject most developers in Mauritius rarely encounter head-on: Enterprise Architecture (EA).</p><p>Ajmal opened by framing the Mauritius context. Few organisations on the island have a formal EA practice — or even know what EA is. Most developers never interact with it directly. Yet, as he pointed out, Enterprise Architecture shapes the very environment in which all development happens. Whether you're aware of it or not, EA decisions (or the absence of them) affect every line of code you ship.</p><p>Before diving into the enterprise side, Ajmal grounded the audience in what "architecture" means in a general sense. He broke it down into four pillars: </p><ul><li>a <strong>blueprint</strong> that guides constructors on how and when to build; </li><li>a <strong>context</strong> — a boundary defined by locality; </li><li>a set of <strong>guidelines</strong> to follow;</li><li>a set of <strong>standards</strong> to adhere to. </li></ul><p>Simple, but a useful mental model for the discussion that followed.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2026/03/ajmal-saifudeen-enterprise-architecture-mscc-meetup-2026.jpeg" class="kg-image" alt="Ajmal Saifudeen explaining about Enterprise Architecture" loading="lazy" width="2000" height="1125" srcset="/content/images/size/w600/2026/03/ajmal-saifudeen-enterprise-architecture-mscc-meetup-2026.jpeg 600w, /content/images/size/w1000/2026/03/ajmal-saifudeen-enterprise-architecture-mscc-meetup-2026.jpeg 1000w, /content/images/size/w1600/2026/03/ajmal-saifudeen-enterprise-architecture-mscc-meetup-2026.jpeg 1600w, /content/images/2026/03/ajmal-saifudeen-enterprise-architecture-mscc-meetup-2026.jpeg 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Ajmal Saifudeen explaining about Enterprise Architecture</span></figcaption></figure><p>One of the striking slides was the iceberg analogy for why good architecture matters. Above the waterline sit the external touchpoints — websites, mobile apps, conversational interfaces — the parts users see. Below the surface lurk the problems that plague organisations without solid architecture: lack of agility, complex and disparate systems, organisational silos, poorly managed data, weak resource planning, and ballooning operational costs. The message was clear — what the customer sees is only the tip; the bulk of the challenge lies underneath.</p><h3 id="what-ea-is-and-what-it-is-not">What EA Is (and What It Is Not)</h3><p>Ajmal offered a concise definition: Enterprise Architecture is the practice of designing how a business operates and how technology supports it, so that technical solutions are purposeful, scalable, and aligned with business goals.</p><p>He was equally clear about common misconceptions. EA is not just documentation. It is not a rigid bureaucracy. And importantly, it is not only for large corporates — a point that resonated with an audience largely composed of developers working across a mix of startup, mid-size, and enterprise environments in Mauritius.</p><p>Ajmal walked through the EA mandate using a layered pyramid model, spanning from Technology Architecture at the base through Integration, Application, and Information Architecture up to Business Architecture at the apex. Each layer maps to a landscape view — technology, integration, application, data, and business — and the whole stack is driven from the top (business strategy) while being enabled from the bottom (technology capabilities). It was a clear illustration of how EA bridges the gap between what the business wants and what IT delivers.</p><p>Sadly, I missed the closing segment of his presentation as I attended a phone call. 😔</p><h2 id="a-12-factor-roadmap-for-modern-php-infrastructure">A 12-Factor Roadmap for Modern PHP Infrastructure</h2><p>After Ajmal's talk, I took the floor with a presentation on applying the twelve-factor app methodology to modern PHP infrastructure — specifically Laravel.</p><h3 id="origins-and-staying-power">Origins and Staying Power</h3><p>I opened with the backstory. The 12-Factor methodology was created by Adam Wiggins and the Heroku engineering team, published in November 2011 at <a href="https://12factor.net" rel="noreferrer">12factor.net</a>. It codified the patterns they observed separating apps that scaled reliably from those that repeatedly failed on their platform. What makes it enduring is that Docker (2013) and Kubernetes (2014) both validated its principles, and today serverless, microservices, and CI/CD pipelines all build on the same foundations. Organisations like Netflix, Spotify, Airbnb, Google Cloud, and AWS publicly endorse and implement 12-Factor patterns across their platforms.</p><h3 id="the-twelve-factors-in-brief">The Twelve Factors in Brief</h3><p>I walked through all twelve factors in two groups. Factors I through VI cover the foundation: a single codebase tracked in version control with many deploys, explicitly declared dependencies via <code>composer.json</code>, configuration stored in environment variables rather than code, backing services (databases, caches, queues) treated as attached resources, strict separation of build/release/run stages, and stateless share-nothing processes.</p><p>Factors VII through XII cover runtime and operations: self-contained port binding (Laravel Octane serving on a port), horizontal scaling via the process model, fast startup and graceful shutdown for disposability, keeping dev/staging/production as similar as possible, treating logs as event streams rather than files, and running admin tasks (migrations, seeders) as one-off processes through Artisan.</p><h3 id="laravel-%C3%97-12-factor-a-natural-fit">Laravel × 12-Factor: A Natural Fit</h3><p>The core of the talk demonstrated how Laravel's architecture aligns with 12-Factor almost by design. The <code>.env</code> file and <code>env()</code> helper give you Factor III (Config) out of the box. <code>composer.json</code> handles Factor II (Dependencies). The driver-based service abstraction in <code>config/database.php</code>, <code>config/cache.php</code>, and <code>config/filesystems.php</code> is Factor IV (Backing Services) in action — you can swap from MySQL to PostgreSQL, from file cache to Redis, or from local disk to S3, all by changing a single environment variable with zero code changes.</p><p>For Factors XI and XII, Laravel's Monolog integration routes logs to <code>stderr</code> in production containers, where the platform (Fluentd, Loki, CloudWatch) handles collection. Artisan provides the CLI for migrations, cache clearing, queue management, and custom one-off commands — in Kubernetes, these run as <code>initContainers</code> or Jobs.</p><h3 id="local-dev-parity-with-laravel-herd">Local Dev Parity with Laravel Herd</h3><p>A significant portion of the talk focused on Factor X (Dev/Prod Parity) through Laravel Herd. Herd is a native PHP development environment for macOS and Windows that provides zero-configuration setup, automatic <code>.test</code> domains with HTTPS, multiple PHP version support (8.1 through 8.5), and built-in services including MySQL, PostgreSQL, Redis, Mailpit, and MinIO for S3-compatible storage. A <code>herd.yml</code> file declares the site configuration declaratively.</p><p>I contrasted the local Herd setup against a production Kubernetes deployment side by side: locally you run file cache, sync queues, and local disk; in production it's Redis cache, Redis queues, and S3 storage — but the application code, routes, and business logic remain identical. Only the <code>.env</code> changes.</p><h2 id="lunch-and-open-discussion">Lunch and Open Discussion</h2><p>Post-lunch, we shifted into an open discussion that brought both topics together. Questions ranged from how EA principles can inform infrastructure decisions to how the twelve-factor approach can serve as a lightweight architectural discipline even in organisations without a formal EA function. The cross-pollination between the two talks made for a richer conversation than either topic would have generated in isolation.</p>
<!--kg-card-begin: html-->
<div class="grid place-items-center">
  <blockquote class="twitter-tweet"><p lang="en" dir="ltr">Thank you to Klanik for hosting us and for the delicious refreshments and lunch.<br><br>A big shout-out to our speakers and to everyone who attended and contributed to the great atmosphere of the meetup! <br>Swipe through to see some of our favorite moments!<a href="https://twitter.com/hashtag/MSCC?src=hash&amp;ref_src=twsrc%5Etfw">#MSCC</a> <a href="https://twitter.com/hashtag/community?src=hash&amp;ref_src=twsrc%5Etfw">#community</a> <a href="https://twitter.com/hashtag/mauritius?src=hash&amp;ref_src=twsrc%5Etfw">#mauritius</a> <a href="https://t.co/F8xakxauPs">pic.twitter.com/F8xakxauPs</a></p>&mdash; MSCC (@MSCraftsman) <a href="https://twitter.com/MSCraftsman/status/2033450486318211304?ref_src=twsrc%5Etfw">March 16, 2026</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</div>
<!--kg-card-end: html-->
]]></content:encoded>
        </item>
        <item>
            <title>How to Deploy a Laravel Application on Google Cloud Run?</title>
            <link>https://sysadmin-journal.com/how-to-deploy-a-laravel-application-on-google-cloud-run</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/how-to-deploy-a-laravel-application-on-google-cloud-run</guid>
            <pubDate>Fri, 13 Mar 2026 09:06:59 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>Google Cloud Platform</category>
            <category>Laravel</category>
            <description>Deploy a Laravel application on Google Cloud Run with PHP 8.5, connect to Cloud SQL PostgreSQL, and scale to zero when idle. A practical guide covering containerization, deployment to the africa-south1 region, and tips for handling storage, sessions, and queues in a serverless environment.</description>
            <media:content url="https://sysadmin-journal.com/content/images/2026/03/laravel-on-google-cloud-run.jpeg" medium="image" />
            <content:encoded><![CDATA[<p>Google Cloud Run is a serverless platform that lets you run containerized applications without managing any infrastructure. You push a container, Cloud Run handles scaling — including scaling to zero when there's no traffic, which means you only pay for what you use.</p><p>For someone like me who has been deploying Laravel applications on traditional Compute Engine instances and Kubernetes clusters, Cloud Run feels like a breath of fresh air for certain workloads. Not everything needs a full-blown GKE cluster or a dedicated VM running 24/7. Sometimes you just need your app to be available, scale when needed, and not cost you anything when it's idle.</p><p>What makes this particularly interesting right now is that Google Cloud has made the <strong>PHP 8.5 runtime generally available on Cloud Run</strong>. If you're running Laravel 12 on PHP 8.5 — as I am — this is great news.</p><h2 id="why-cloud-run-for-laravel">Why Cloud Run for Laravel?</h2><p>Laravel is traditionally deployed on a web server like Nginx or Apache, sitting behind PHP-FPM, on a Linux server you manage yourself. That's perfectly fine for production workloads that need full control. But for staging environments, internal tools, API backends, or even personal projects, Cloud Run removes a lot of the operational overhead.</p><p>Key advantages that stand out for me:</p><ul><li><strong>Scale to zero</strong> — no traffic, no cost. This is ideal for development and staging environments.</li><li><strong>Automatic HTTPS</strong> — Cloud Run provisions and manages TLS certificates for you.</li><li><strong>Built-in revision management</strong> — every deployment creates a new revision, making rollbacks trivial.</li><li><strong>Regional deployment</strong> — and yes, <code>africa-south1</code> (Johannesburg) is supported, which means latency from Mauritius is reasonable thanks to the submarine cable connectivity.</li></ul><h2 id="preparing-your-laravel-application">Preparing Your Laravel Application</h2><p>Before deploying to Cloud Run, your Laravel application needs to be containerized. If you've worked with Docker before — and if you're deploying Laravel in 2026, you probably have — this is straightforward.</p><p>Create a <code>Dockerfile</code> in the root of your Laravel project:</p><pre><code>FROM php:8.5-apache

RUN apt-get update &amp;&amp; apt-get install -y \
    libpng-dev \
    libonig-dev \
    libxml2-dev \
    zip \
    unzip \
    &amp;&amp; docker-php-ext-install pdo_pgsql mbstring exif pcntl bcmath gd

RUN a2enmod rewrite

ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

WORKDIR /var/www/html
COPY . .

RUN composer install --no-dev --optimize-autoloader
RUN php artisan config:cache &amp;&amp; php artisan route:cache &amp;&amp; php artisan view:cache

RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache

EXPOSE 8080

RUN sed -i 's/80/8080/g' /etc/apache2/sites-available/000-default.conf /etc/apache2/ports.conf

CMD ["apache2-foreground"]</code></pre><p>A couple of things to note here. Cloud Run expects your container to listen on port <code>8080</code> by default — that's why we're modifying the Apache configuration. I'm using <code>pdo_pgsql</code> because I connect to Cloud SQL PostgreSQL, but swap that for <code>pdo_mysql</code> if you're on MySQL.</p><h2 id="deploying-to-cloud-run">Deploying to Cloud Run</h2><p>With the Dockerfile ready, deploying is a single <code>gcloud</code> command. First, make sure you have the Google Cloud SDK installed and authenticated.</p><pre><code>gcloud run deploy my-laravel-app \
    --source . \
    --region africa-south1 \
    --allow-unauthenticated \
    --set-env-vars APP_KEY=base64:YOUR_APP_KEY_HERE \
    --set-env-vars APP_ENV=production \
    --set-env-vars LOG_CHANNEL=stderr \
    --set-env-vars DB_CONNECTION=pgsql \
    --set-env-vars DB_HOST=/cloudsql/PROJECT_ID:africa-south1:INSTANCE_NAME \
    --add-cloudsql-instances PROJECT_ID:africa-south1:INSTANCE_NAME \
    --memory 512Mi \
    --cpu 1 \
    --min-instances 0 \
    --max-instances 5</code></pre><p>The <code>--source .</code> flag tells Cloud Run to build the container image for you using Cloud Build — so you don't even need to push to Artifact Registry manually. Google handles the build and deployment in one step.</p><p>Notice the <code>--min-instances 0</code> — this is what enables scale-to-zero. For a staging environment or a low-traffic application, this keeps your costs minimal. For production, you might want to set <code>--min-instances 1</code> to avoid cold starts.</p><p>The <code>LOG_CHANNEL=stderr</code> setting is important. Cloud Run captures <code>stderr</code> output and sends it to Cloud Logging, so you get your Laravel logs in the Google Cloud Console without any additional configuration.</p><h2 id="connecting-to-cloud-sql">Connecting to Cloud SQL</h2><p>If you're using Cloud SQL — and I've written about <a href="https://sysadmin-journal.com/google-cloud-workload-identity-federation-a-guide-to-keyless-authentication-for-multi-cloud-environments/">scaling Laravel with Cloud SQL read replicas</a> before — Cloud Run has built-in support for connecting through the Cloud SQL Auth Proxy. The <code>--add-cloudsql-instances</code> flag in the deploy command sets this up automatically.</p><p>The connection happens over a Unix socket, which is why the <code>DB_HOST</code> is set to <code>/cloudsql/PROJECT_ID:REGION:INSTANCE_NAME</code> rather than an IP address. This is secure by default — no public IP required on your Cloud SQL instance.</p><h2 id="what-about-file-storage">What About File Storage?</h2><p>Laravel's default file storage driver writes to the local filesystem. That won't work on Cloud Run because the container filesystem is ephemeral — it gets wiped on every new deployment or instance scale event. For file uploads and storage, switch to Google Cloud Storage using the <code>league/flysystem-google-cloud-storage</code> package:</p><pre><code>composer require league/flysystem-google-cloud-storage</code></pre><p>Configure a <code>gcs</code> disk in your <code>config/filesystems.php</code> and set <code>FILESYSTEM_DISK=gcs</code> in your environment variables. Cloud Run's service account will handle authentication automatically if you've granted it the <code>Storage Object Admin</code> role — no API keys needed.</p><h2 id="things-to-keep-in-mind">Things to Keep in Mind</h2><p>Cloud Run is stateless. This means you cannot rely on the local filesystem for sessions or cache. Use <strong>Redis</strong> (via Memorystore) or <strong>database sessions</strong> instead. Similarly, Laravel's scheduler (<code>php artisan schedule:run</code>) doesn't work in Cloud Run's request-driven model. For scheduled tasks, pair Cloud Scheduler with Cloud Run by having the scheduler trigger an HTTP endpoint on your application.</p><p>Queue workers are another consideration. Cloud Run isn't designed for long-running processes, so running <code>php artisan queue:work</code> inside a Cloud Run container isn't ideal. Instead, use Cloud Tasks to dispatch jobs to a dedicated Cloud Run endpoint, or run your workers on a Compute Engine instance or GKE.</p><h2 id="wrapping-up">Wrapping Up</h2><p>Cloud Run sits in a sweet spot between the full control of a Compute Engine VM and the abstraction of a purely serverless function. For Laravel applications that don't need persistent background processes or filesystem state, it's an excellent deployment target — especially now that PHP 8.5 is fully supported.</p><p>I've been using it for a couple of internal tools and staging environments, and the cost savings alone from scale-to-zero make it worth exploring. If you're already on Google Cloud and deploying Laravel, it's worth giving Cloud Run a try on your next project.</p>]]></content:encoded>
        </item>
        <item>
            <title>What your appearance says before you speak — Lessons from PMI Mauritius</title>
            <link>https://sysadmin-journal.com/what-your-appearance-says-before-you-speak-lessons-from-pmi-mauritius</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/what-your-appearance-says-before-you-speak-lessons-from-pmi-mauritius</guid>
            <pubDate>Wed, 18 Feb 2026 19:43:00 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>PMI Chapter of Mauritius</category>
            <description>PMI Mauritius hosted an insightful session with Nadjma Rawat on professional image and personal branding. Learn how the &quot;RAG project management framework&quot; applies to grooming, clothing, and body language — and what your appearance says before you speak.</description>
            <content:encoded><![CDATA[<p>Today, around twenty of us gathered at Rockfin Training Institute in Ébène House for a <a href="https://pmimauritius.com/event/the-rag-status-of-your-professional-image" rel="noreferrer">PMI Mauritius Chapter meetup</a> that turned out to be far more thought-provoking than a typical project management session. The theme: <strong>"The RAG Status of Your Professional Image: Engineering Authority and Influence."</strong></p><p>The evening began with a warm welcome from Sareeta Nundloll-Goundan, President of PMI Mauritius, who introduced the speaker and noted that this was actually Nadjma Rawat's second time delivering her talk on <em>grooming and personal branding</em> at PMI Mauritius — a testament to how well it had been received the first time around.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2026/02/sareeta-nundloll-goundan-pmi-mauritius.jpeg" class="kg-image" alt="Sareeta Nundloll-Goundan, President of PMI Mauritius Chapter, welcoming the attendees" loading="lazy" width="1000" height="750" srcset="/content/images/size/w600/2026/02/sareeta-nundloll-goundan-pmi-mauritius.jpeg 600w, /content/images/2026/02/sareeta-nundloll-goundan-pmi-mauritius.jpeg 1000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Sareeta Nundloll-Goundan, President of PMI Mauritius Chapter, welcoming the attendees</span></figcaption></figure><p>Nadjma wasted no time and opened with a simple game. She called out words like <em>danger, calm, love, nature,</em> and asked us to shout out the first colour that came to mind. The room responded almost in unison most of the time. It was a striking demonstration of how deeply colour is wired into our psychology before any rational thought kicks in.</p><p>That exercise set the stage for her core argument: <strong>colour perception changes body chemistry.</strong> The human brain processes visual data in milliseconds and makes snap judgements — about safety, competence, and trustworthiness — before a single word is spoken. This is what she called <em>Visual Intelligence</em>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2026/02/nadja-rawat-executive-image-consultant.jpeg" class="kg-image" alt="Nadjma Rawat explaining the Colour Psychology" loading="lazy" width="1000" height="750" srcset="/content/images/size/w600/2026/02/nadja-rawat-executive-image-consultant.jpeg 600w, /content/images/2026/02/nadja-rawat-executive-image-consultant.jpeg 1000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Nadjma Rawat explaining the Colour Psychology</span></figcaption></figure><h2 id="from-project-dashboards-to-people">From Project Dashboards to People</h2><p>If you work in project management, you already know RAG status. Red, Amber, Green — the traffic light system used to communicate the health of a project's scope, schedule, and budget at a glance.</p><p>Nadjma's insight was to flip this framework inward. <strong>Your professional image is your personal RAG report.</strong> Stakeholders, clients, and colleagues are unconsciously reading you the moment you walk into a room or join a video call.</p><p>The slide she presented on <strong>Red Alerts</strong> made this concrete:</p>
<!--kg-card-begin: html-->
<table>
<thead>
<tr>
<th>Category</th>
<th>🔴 Red (High Noise / Crisis)</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Clothes</strong></td>
<td>Wrinkled, stained, missing buttons, wrong fit</td>
</tr>
<tr>
<td><strong>Footwear</strong></td>
<td>Scuffed, dirty, worn-out soles</td>
</tr>
<tr>
<td><strong>Grooming</strong></td>
<td>Unmanaged hair, visible sweat, dirty nails</td>
</tr>
<tr>
<td><strong>Body Language</strong></td>
<td>Slumping, avoiding eye contact, closed arms</td>
</tr>
<tr>
<td><strong>Auditory</strong></td>
<td>Dangling jewellery, loudspeaker in open spaces</td>
</tr>
<tr>
<td><strong>Digital</strong></td>
<td>Cluttered video background, poor lighting</td>
</tr>
<tr>
<td><strong>Emails &amp; Presentations</strong></td>
<td>Typos, no subject line, unnecessary Reply All</td>
</tr>
</tbody>
</table>
<!--kg-card-end: html-->
<p>And here's what made it land: she backed each category with a concrete impact figure.</p>
<!--kg-card-begin: html-->
<table>
<thead>
<tr>
<th>Category</th>
<th>Impact</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Garment Care</strong></td>
<td>−43% Competence perceived</td>
</tr>
<tr>
<td><strong>Footwear</strong></td>
<td>−30% Stability</td>
</tr>
<tr>
<td><strong>Grooming</strong></td>
<td>−25% Presence</td>
</tr>
<tr>
<td><strong>Body Language</strong></td>
<td>−40% Authority</td>
</tr>
<tr>
<td><strong>Auditory signals</strong></td>
<td>−20% Confidence</td>
</tr>
<tr>
<td><strong>Digital background</strong></td>
<td>−35% Trust</td>
</tr>
<tr>
<td><strong>Emails &amp; Presentations</strong></td>
<td>−50% Efficiency</td>
</tr>
</tbody>
</table>
<!--kg-card-end: html-->
<p>The grooming point particularly stuck with me: <em>"Poor grooming suggests you are a victim of the project's pressure rather than its manager."</em> That framing is hard to argue with. 🤔</p><h2 id="clothing-dressing-with-intention">Clothing: Dressing with Intention</h2><p>One of the most practical segments of Nadjma's session was on clothing — not fashion, but <strong>intentional dressing</strong>. The key principle she opened with: <em>focus on how the clothes feel and move</em>, not just how they look on the hanger.</p><p>The slide she showed contrasted two silhouettes side by side — one labelled <strong>Unintentional</strong>, the other <strong>Intentional</strong>. The difference wasn't expensive clothing. It was controlled fabric contrast, a cohesive colour family, and a silhouette that balanced the body. The unintentional look had mismatched textures, random colour temperature, and an uneven silhouette. Small details, massive difference in perception.</p><h3 id="fabric-the-breathability-test">Fabric: The Breathability Test</h3><p>Nadjma was direct about fabric choices: stick to <strong>cotton, linen-blends, and "Cool Wool."</strong> Avoid 100% polyester — it traps heat, creates sweat patches, and often has a cheap shine that reads as low quality under lighting, whether in a boardroom or on a video call.</p><p>Her practical tip before buying: <strong>scrunch a bit of the fabric in your hand for 5 seconds</strong>. If it bounces back, it's a high-twist fabric that resists wrinkles. If it stays creased, it will look creased by 10 a.m. on your most important day. 🤯</p><h3 id="the-fit-rules">The Fit Rules</h3><p>Fit matters more than brand. Nadjma shared two simple checks:</p><ul><li><strong>The Shoulder Anchor</strong> — the seam of your shirt or jacket should sit exactly where your arm meets your shoulder. If it droops or pulls inward, the fit is wrong regardless of the size on the label.</li><li><strong>The One-Finger Rule</strong> — you should be able to slide exactly one finger comfortably between your shirt collar and your neck. Too tight reads as uncomfortable and anxious. Too loose looks careless.</li></ul><h2 id="from-red-alert-to-green-zone-%E2%9C%85">From Red Alert to Green Zone ✅</h2><p>The takeaway isn't that you need an expensive wardrobe or a perfect appearance. It's about intentionality. A Green status isn't flawless — it's <em>in control.</em> Clean clothes that fit, polished shoes, a tidy video background, structured emails with a clear subject line. These are all signals that say: <em>I have my house in order, and I can manage yours too.</em></p><p>For PMs especially, this matters because you are often the first point of contact for senior stakeholders and investors. Your image is the dashboard they're reading long before your project plan is ever opened.</p><h2 id="a-different-kind-of-personal-project-to-manage">A Different Kind of (Personal) Project to Manage</h2><p>Lesson learned from Nadjma's session is that managing your professional image isn't vanity — it's stakeholder communication. Every detail you present visually is data that people are processing, consciously or not.</p><p>The next time you're preparing for a big presentation or a stakeholder meeting, run a quick RAG check on yourself. 🧐 Not just your slides — your clothes, your background, your posture, your email subject line. It takes five minutes and could change how the entire room receives what you have to say. Make your own checklist and go through it.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2026/02/PMI_Mauritius_Group_Picture.jpeg" class="kg-image" alt="PMI Chapter of Mauritius — the traditional group photo" loading="lazy" width="1000" height="562" srcset="/content/images/size/w600/2026/02/PMI_Mauritius_Group_Picture.jpeg 600w, /content/images/2026/02/PMI_Mauritius_Group_Picture.jpeg 1000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">PMI Chapter of Mauritius — the traditional group photo</span></figcaption></figure>]]></content:encoded>
        </item>
        <item>
            <title>openSUSE Leap 16.0 is now available on Google Cloud Platform</title>
            <link>https://sysadmin-journal.com/opensuse-leap-16-0-is-now-available-on-google-cloud-platform</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/opensuse-leap-16-0-is-now-available-on-google-cloud-platform</guid>
            <pubDate>Wed, 18 Feb 2026 07:55:36 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>openSUSE</category>
            <category>Google Cloud Platform</category>
            <description>openSUSE Leap 16.0 is now available on Google Cloud Platform with x86_64 and Arm64 images. You can launch a Compute Engine instance by selecting openSUSE Leap 16.0 under Public images. The Cloud Observability Ops Agent is not yet supported.</description>
            <content:encoded><![CDATA[<figure class="kg-card kg-image-card"><img src="https://sysadmin-journal.com/content/images/2026/02/opensuse-leap-on-google-cloud-platform-2026.png" class="kg-image" alt="" loading="lazy" width="2000" height="1335" srcset="/content/images/size/w600/2026/02/opensuse-leap-on-google-cloud-platform-2026.png 600w, /content/images/size/w1000/2026/02/opensuse-leap-on-google-cloud-platform-2026.png 1000w, /content/images/size/w1600/2026/02/opensuse-leap-on-google-cloud-platform-2026.png 1600w, /content/images/2026/02/opensuse-leap-on-google-cloud-platform-2026.png 2184w" sizes="(min-width: 720px) 720px"></figure><p>openSUSE Leap 16.0 is now available as a public image on Google Cloud Platform. Both <code>x86_64</code> and <code>Arm64</code> images were built on 16 February 2026 and are ready for use with Compute Engine instances.</p><h2 id="launching-an-instance-with-opensuse-leap-160">Launching an instance with openSUSE Leap 16.0</h2><p>Getting started with Leap 16.0 on GCP is straightforward. From the Google Cloud Console, navigate to <strong>Compute Engine &gt; VM instances</strong> and click <strong>Create Instance</strong>.</p><p>In the instance configuration page, scroll down to the <strong>OS and storage</strong> section and click <strong>Change</strong> to open the boot disk configuration panel. </p><figure class="kg-card kg-image-card"><img src="https://sysadmin-journal.com/content/images/2026/02/gcp-compute-os-change.png" class="kg-image" alt="" loading="lazy" width="1832" height="662" srcset="/content/images/size/w600/2026/02/gcp-compute-os-change.png 600w, /content/images/size/w1000/2026/02/gcp-compute-os-change.png 1000w, /content/images/size/w1600/2026/02/gcp-compute-os-change.png 1600w, /content/images/2026/02/gcp-compute-os-change.png 1832w" sizes="(min-width: 720px) 720px"></figure><p>Under the <strong>Public images</strong> tab, select <strong>openSUSE</strong> from the <strong>Operating system</strong> dropdown. You will then see <strong>openSUSE Leap</strong> listed under the <strong>Version</strong> dropdown — select the <strong>openSUSE Leap 16.0</strong> entry. Choose your preferred boot disk type and size, then click <strong>Select</strong> to confirm.</p><figure class="kg-card kg-image-card"><img src="https://sysadmin-journal.com/content/images/2026/02/gcp-compute-select-opensuse-leap.png" class="kg-image" alt="" loading="lazy" width="1134" height="1294" srcset="/content/images/size/w600/2026/02/gcp-compute-select-opensuse-leap.png 600w, /content/images/size/w1000/2026/02/gcp-compute-select-opensuse-leap.png 1000w, /content/images/2026/02/gcp-compute-select-opensuse-leap.png 1134w" sizes="(min-width: 720px) 720px"></figure><p>From there, configure the rest of your instance settings — machine type, networking, firewall rules — as you normally would, and click <strong>Create</strong> to launch your new Leap 16.0 VM.</p><h2 id="a-note-on-google-cloud-observability">A note on Google Cloud Observability</h2><p>During instance creation, you will notice the <strong>Observability - Ops Agent</strong> section near the bottom of the configuration page. For supported operating systems, this provides a convenient <strong>Install Ops Agent for Monitoring and Logging</strong> checkbox that automatically installs the agent when the VM boots. However, with openSUSE Leap 16.0 selected as the boot disk image, this checkbox is greyed out and displays the message: <em>"Not available for the selected image."</em> This is because the automated installation method does not yet support Leap 16.0.</p><figure class="kg-card kg-image-card"><img src="https://sysadmin-journal.com/content/images/2026/02/gcp-compute-ops-agent.png" class="kg-image" alt="" loading="lazy" width="1084" height="444" srcset="/content/images/size/w600/2026/02/gcp-compute-ops-agent.png 600w, /content/images/size/w1000/2026/02/gcp-compute-ops-agent.png 1000w, /content/images/2026/02/gcp-compute-ops-agent.png 1084w" sizes="(min-width: 720px) 720px"></figure><p>You might think the next step would be to install the Ops Agent manually via the command line. Google's <a href="https://docs.cloud.google.com/stackdriver/docs/solutions/agents/ops-agent/installation">installation documentation</a> provides a script for exactly this:</p><pre><code>curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh
sudo bash add-google-cloud-ops-agent-repo.sh --also-install</code></pre><p>Unfortunately, this does not work on Leap 16.0 either — and the reason is buried in how the script identifies your operating system.</p><h2 id="why-does-the-script-fail">Why does the script fail?</h2><p>The script reads <code>/etc/os-release</code> to determine the OS family and version. For any SUSE-based distribution, including openSUSE Leap, it routes to a <code>handle_suse</code> function via a case statement in <code>main()</code>:</p><pre><code>sles|opensuse-leap) handle_suse ;;</code></pre><p>Inside <code>handle_suse</code>, the <code>add_repo()</code> function extracts the major version number and constructs a SLES-based repository codename:</p><pre><code>local SUSE_VERSION=${VERSION_ID%%.*}
local CODENAME="${REPO_CODENAME:-"sles${SUSE_VERSION}"}"</code></pre><p>On openSUSE Leap 16.0, <code>VERSION_ID</code> is <code>16.0</code>, so <code>SUSE_VERSION</code> resolves to <code>16</code> and the codename becomes <code>sles16</code>. The script then attempts to add a repository at:</p><pre><code>https://packages.cloud.google.com/yum/repos/google-cloud-ops-agent-sles16-$basearch-all</code></pre><p>The problem is that this repository simply does not exist. Google currently publishes Ops Agent packages for <code>sles12</code> and <code>sles15</code>, but there is no <code>sles16</code> repository — SLES 16 itself has not been released yet. The script makes no distinction between SLES and openSUSE Leap; it treats both as the same platform and derives the repo path purely from the major version number. The result is a <code>refresh_failed</code> error telling you to check your network connectivity and verify that you are running a supported distribution.</p><p>This leaves openSUSE Leap users on GCP in a bit of a gap. While the Ops Agent officially supports Leap 15.6, that image is no longer available on GCP — only Leap 16.0 is offered as a public image. So the supported version cannot be deployed, and the deployable version is not supported. Until Google publishes a compatible repository for Leap 16.0, there is no straightforward path to running the Ops Agent on an openSUSE instance on GCP. Keep an eye on the <a href="https://docs.cloud.google.com/stackdriver/docs/solutions/agents/ops-agent">Ops Agent supported platforms list</a> for updates.</p><p>Installing the Leap 15.6 version of the agent isn't a viable workaround due to missing libraries.</p><pre><code>Problem: 1: nothing provides 'libcrypto.so.1.1()(64bit)' needed by the to be installed google-cloud-ops-agent-2.63.0-1.sles15.x86_64</code></pre><hr><p>As always, the <a href="https://opensuse.org" rel="noreferrer">openSUSE community</a> welcomes feedback and bug reports — if you run into issues with the GCP images that are specific to the openSUSE project, report them through the <a href="https://bugzilla.opensuse.org/buglist.cgi?component=Cloud%3AImages&amp;product=openSUSE%20Distribution&amp;resolution=---" rel="noreferrer">openSUSE Bugzilla</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title>openSUSE Board Election 2025 has been announced</title>
            <link>https://sysadmin-journal.com/opensuse-board-election-2025-has-been-announced</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/opensuse-board-election-2025-has-been-announced</guid>
            <pubDate>Tue, 17 Feb 2026 12:21:13 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>openSUSE</category>
            <description>The openSUSE Project is a worldwide community effort promoting the use of Linux everywhere. The openSUSE Board guides the project&#039;s direction and facilitates community collaboration. Two seats are now open as the 2025 Board Election begins — get involved by running or casting your vote in March.</description>
            <content:encoded><![CDATA[<p>The openSUSE Board Election 2025 has officially been announced. Originally scheduled for November/December 2025, the election was postponed due to a backlog of tasks on the membership database. The Election Committee – Ariez Vachha, Edwin Zakaria, Lubos Kocman and Eddy Lareine – has now <a href="https://lists.opensuse.org/archives/list/project@lists.opensuse.org/thread/MVYPHOGGKS3EPPR45EDGLY65CXM74WPB/" rel="noreferrer">published</a> the election schedule.</p><p>Two seats on the <a href="https://en.opensuse.org/openSUSE:Board" rel="noreferrer">openSUSE Board</a> are up for grabs as Simon Lees and Shawn W Dunn complete their mandate. This is a great opportunity for community members who want to help shape the direction of the openSUSE Project.</p><figure class="kg-card kg-image-card"><img src="https://sysadmin-journal.com/content/images/2026/02/opensuse-board-election-2025-poster-2.jpeg" class="kg-image" alt="" loading="lazy" width="2000" height="2829" srcset="/content/images/size/w600/2026/02/opensuse-board-election-2025-poster-2.jpeg 600w, /content/images/size/w1000/2026/02/opensuse-board-election-2025-poster-2.jpeg 1000w, /content/images/size/w1600/2026/02/opensuse-board-election-2025-poster-2.jpeg 1600w, /content/images/size/w2400/2026/02/opensuse-board-election-2025-poster-2.jpeg 2400w" sizes="(min-width: 720px) 720px"></figure><h2 id="key-dates">Key dates</h2><ul><li><strong>27 February</strong> – Deadline for candidates to declare their intention</li><li><strong>28 February</strong> – Candidate slate published</li><li><strong>1 March</strong> – Voting opens</li><li><strong>9 March</strong> – Results announced (ballots remain open for one week)</li></ul><h2 id="who-can-participate">Who can participate?</h2><p>Only active openSUSE members are eligible to vote. If you're a contributor but not yet a member, you can apply for membership at any time. Members who join after the candidate slate is published on 28 February can still vote, but will not be able to run as a candidate.</p><p>If your membership is approved after voting has begun, reach out to the election officials to inform them. Membership approval and voting in the election is not an automatic process. The election officials will use an up to data membership database for the election process on the 1 March when opening the ballots. Thus, membership approvals after that date have to be informed to the officials.</p><p>Details on how to become a member are available on the wiki: <a href="https://en.opensuse.org/openSUSE:Members#How_to_become_a_Member">openSUSE:Members</a></p><h2 id="get-involved">Get involved</h2><p>If you're interested in running, reach out to the Election Committee via their <a href="mailto:election-officials@opensuse.org" rel="noreferrer">mailing list</a> before the 27th of February. The announcement was made on the openSUSE Project mailing list and social media, and the <a href="https://en.opensuse.org/openSUSE:Board_election">election wiki page</a> has been updated with full details.</p><p>The openSUSE Board plays a vital role in guiding the project. Whether you choose to run or simply cast your vote, your participation matters.</p>]]></content:encoded>
        </item>
        <item>
            <title>How to Bring Your Own IP to Google Cloud Platform?</title>
            <link>https://sysadmin-journal.com/how-to-bring-your-own-ip-to-google-cloud-platform</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/how-to-bring-your-own-ip-to-google-cloud-platform</guid>
            <pubDate>Mon, 16 Feb 2026 21:17:34 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>Google Cloud Platform</category>
            <description>Learn how to bring your AFRINIC-allocated IP addresses to Google Cloud Platform using BYOIP. This guide covers the process from ROA configuration to deploying addresses on Compute Engine, with practical insights for organizations in Mauritius and Africa using the South Africa (Johannesburg) region.</description>
            <content:encoded><![CDATA[<figure class="kg-card kg-image-card"><img src="https://sysadmin-journal.com/content/images/2026/02/byoip-cover-image-3.png" class="kg-image" alt="" loading="lazy" width="1600" height="900" srcset="/content/images/size/w600/2026/02/byoip-cover-image-3.png 600w, /content/images/size/w1000/2026/02/byoip-cover-image-3.png 1000w, /content/images/2026/02/byoip-cover-image-3.png 1600w" sizes="(min-width: 720px) 720px"></figure><p>When migrating workloads to the cloud, one of the most overlooked challenges is IP address management. Organizations that have built their reputation, whitelists, and infrastructure around specific IP addresses often face a difficult choice: abandon those trusted addresses or maintain expensive on-premises equipment just to keep using them. This challenge is particularly relevant in Mauritius, where Internet leased circuit offerings remain "unfortunately" expensive, making it impractical for small networks to manage their own IP number resources on-premises. Google Cloud's <strong>Bring Your Own IP (BYOIP)</strong> feature solves this problem, and for organizations in Africa and the Indian Ocean region holding AFRINIC allocations, it opens up exciting possibilities.</p><p>This article is based on my experience managing a Public IP Address Prefix allocated by AFRINIC to the company I work for. I'll share the practical insights gained through this process, from understanding the Regional Internet Registry ecosystem to the actual implementation steps that made our IPv4 migration to Google Cloud seamless.</p><h2 id="understanding-regional-internet-registries-and-afrinic">Understanding Regional Internet Registries and AFRINIC</h2><p>Before diving into the technical bits, it's worth understanding where your IP addresses come from. I find that many developers and even sysadmins don't fully grasp the infrastructure that makes global Internet addressing possible.</p><p>The Internet's addressing system is coordinated through a hierarchical structure. At the top sits the <a href="https://www.iana.org/" rel="noreferrer">Internet Assigned Numbers Authority (IANA)</a>, which allocates large blocks of IP addresses to five Regional Internet Registries (RIRs). These RIRs are nonprofit organizations that manage the distribution of Internet number resources within their geographic regions:</p><ul><li><a href="https://www.afrinic.net/" rel="noreferrer"><strong>AFRINIC</strong></a> (African Network Information Centre) — Africa and the Indian Ocean</li><li><a href="https://www.apnic.net/" rel="noreferrer"><strong>APNIC</strong></a> (Asia-Pacific Network Information Centre) — East, South, and Southeast Asia, plus Oceania</li><li><a href="https://www.arin.net/" rel="noreferrer"><strong>ARIN</strong></a> (American Registry for Internet Numbers) — North America, parts of the Caribbean, and Antarctica</li><li><a href="https://www.lacnic.net/" rel="noreferrer"><strong>LACNIC</strong></a> (Latin America and Caribbean Network Information Centre) — South and Central America</li><li><a href="https://www.ripe.net/" rel="noreferrer"><strong>RIPE NCC</strong></a> (Réseaux IP Européens Network Coordination Centre) — Europe, the Middle East, and Central Asia</li></ul><p>AFRINIC is headquartered in Ébène, Mauritius — yes, right here on our island! It was established in 2004 and received ICANN's final recognition in April 2005, making it the youngest of the five RIRs. As a not-for-profit organization, AFRINIC serves approximately 2,400 members across 56 countries, including Internet service providers, Internet exchange points, governments, academic institutions, and businesses that operate networks.</p><p>For a company based in Mauritius holding a <code>/24</code> Public Advertised Prefix from AFRINIC, this means you have 256 IPv4 addresses that are officially registered and recognized globally as belonging to your organization.</p><h2 id="why-bring-your-own-ip-byoip-matters-to-us-in-africa-particularly-mauritius">Why Bring Your Own IP (BYOIP) Matters to us (in Africa, particularly Mauritius)?</h2><p>Google Cloud <a href="https://cloud.google.com/blog/products/networking/bring-your-own-ip-addresses-the-secret-to-bitlys-shortened-cloud-migration" rel="noreferrer">announced BYOIP</a> in October 2019, becoming the first cloud provider to make this feature globally available across all its regions. However, here's the catch for us in Africa — while BYOIP was available globally since 2019, Google Cloud had no region on the African continent until January 2024, when the <a href="https://cloud.google.com/blog/products/infrastructure/heita-south-africa-new-cloud-region" rel="noreferrer">africa-south1 region (Johannesburg, South Africa) became operational</a>. This was a significant milestone as it marked Google Cloud's first presence in Africa.</p><p>For organizations in Mauritius and across the AFRINIC region, this meant that for nearly five years, using BYOIP required routing traffic through distant regions like <code>europe-west1</code> or <code>me-west1</code> (Tel Aviv). Not ideal for latency-sensitive applications. The Johannesburg region changes everything — we now have a Google Cloud region connected to Mauritius via three submarine cables.</p><h2 id="the-byoip-architecture-on-google-cloud">The BYOIP Architecture on Google Cloud</h2><p>Google Cloud's BYOIP implementation revolves around two key concepts: the Public Advertised Prefix (PAP) and Public Delegated Prefixes (PDPs).</p><p>A <strong>Public Advertised Prefix</strong> is your IP address block as registered with Google Cloud. This is where ownership verification happens through Route Origin Authorization (ROA) and reverse DNS validation. After verification is complete, Google configures the announcement of this prefix to the Internet, but the prefix is not advertised until it is provisioned. The provisioning process takes approximately four weeks.</p><p>A <strong>Public Delegated Prefix</strong> is a subdivision of your PAP that you configure for use in a specific scope—either a particular region or globally. You can break up your PAP into multiple PDPs to distribute addresses across different regions or use cases. These PDPs can be further divided into sub-prefixes, giving you granular control over how your IP addresses are used.</p><h2 id="setting-up-byoip">Setting Up BYOIP</h2><p>Let me walk you through how I configured our company's AFRINIC-allocated <code>/24</code> prefix on Google Cloud. The process requires coordination between your Regional Internet Registry (AFRINIC in our case) and Google Cloud.</p><h3 id="prerequisites-and-roa-configuration">Prerequisites and ROA Configuration</h3><p>Before touching the Google Cloud Console, you need to create a Route Origin Authorization (ROA) with AFRINIC. The ROA is a cryptographically signed statement that authorizes Google's Autonomous System Number (ASN) to advertise your IP prefix. Google uses ASN 396982 for BYOIP announcements.</p><p>The ROA must include your prefix range, Google's ASN, and an appropriate expiration date. This authorization is registered with the Resource Public Key Infrastructure (RPKI), which allows BGP routers worldwide to verify the legitimacy of route announcements.</p><p>I won't go into the details of creating the ROA on AFRINIC's portal here as it is purely <a href="https://afrinic.net/support/rpki/create-add-rpki-roa" rel="noreferrer">textbook instruction</a>. If you're an AFRINIC member, you should be familiar with their MyAFRINIC portal where the RPKI management is done.</p><h3 id="creating-the-public-advertised-prefix">Creating the Public Advertised Prefix</h3><p>With the ROA in place, navigate to the VPC network section in Google Cloud Console. The process begins by creating your Public Advertised Prefix.</p><figure class="kg-card kg-image-card"><img src="https://sysadmin-journal.com/content/images/2026/02/create-pap-1.jpg" class="kg-image" alt="" loading="lazy" width="698" height="239" srcset="/content/images/size/w600/2026/02/create-pap-1.jpg 600w, /content/images/2026/02/create-pap-1.jpg 698w"></figure><p>Click on "Add PAP" to start creating your Public Advertised Prefix. The first step requires you to enter details for the prefix you want to bring to Google Cloud. Notice the reminder about Route Origin Authorisation Verification — this is why we set up the ROA with AFRINIC beforehand.</p><figure class="kg-card kg-image-card"><img src="https://sysadmin-journal.com/content/images/2026/02/enter-pap-details.jpg" class="kg-image" alt="" loading="lazy" width="823" height="836" srcset="/content/images/size/w600/2026/02/enter-pap-details.jpg 600w, /content/images/2026/02/enter-pap-details.jpg 823w" sizes="(min-width: 720px) 720px"></figure><p>You'll need to provide a name for your PAP, select the IP version (IPv4 or IPv6), enter your prefix in CIDR notation, and choose the scope (Regional or Global). The form even shows an example format: <code>203.0.113.0/24</code>.</p><p>After entering your prefix details, Google Cloud will ask you to confirm ownership. Pay attention to the warning here — this information cannot be edited later. Double-check that the net range and CIDR are correct before proceeding.</p><p>The final step is validation. Google Cloud verifies your ownership through two methods: DNS validation and Route origin attestation. For DNS validation, you'll need to create a PTR record using the IP address and name provided. The ROA validation checks against the RPKI to confirm that you've authorized Google's ASN to announce your prefix.</p><figure class="kg-card kg-image-card"><img src="https://sysadmin-journal.com/content/images/2026/02/byoip-validation.jpg" class="kg-image" alt="" loading="lazy" width="837" height="736" srcset="/content/images/size/w600/2026/02/byoip-validation.jpg 600w, /content/images/2026/02/byoip-validation.jpg 837w" sizes="(min-width: 720px) 720px"></figure><p>Once you've created the PTR record and your ROA is properly configured with AFRINIC, click the checkbox to confirm and hit Validate. When both validations show "Completed", you're good to go!</p><p>After validation completes, you'll see your PAP listed with "Prefix configuration in progress" status. This is where patience comes in.</p><figure class="kg-card kg-image-card"><img src="https://sysadmin-journal.com/content/images/2026/02/byoip-config-progress.jpg" class="kg-image" alt="" loading="lazy" width="1019" height="206" srcset="/content/images/size/w600/2026/02/byoip-config-progress.jpg 600w, /content/images/size/w1000/2026/02/byoip-config-progress.jpg 1000w, /content/images/2026/02/byoip-config-progress.jpg 1019w" sizes="(min-width: 720px) 720px"></figure><p>The full provisioning process takes about four weeks. This isn't Google being slow— it's the time needed for route propagation across global BGP infrastructure to ensure stable, reliable routing.</p><p>Once provisioning completes, the status changes to "Ready to announce". This is the moment you've been waiting for!</p><figure class="kg-card kg-image-card"><img src="https://sysadmin-journal.com/content/images/2026/02/ready-to-announce.jpg" class="kg-image" alt="" loading="lazy" width="1307" height="169" srcset="/content/images/size/w600/2026/02/ready-to-announce.jpg 600w, /content/images/size/w1000/2026/02/ready-to-announce.jpg 1000w, /content/images/2026/02/ready-to-announce.jpg 1307w" sizes="(min-width: 720px) 720px"></figure><p>Click on the three-dot menu under Actions and select "Announce" to start advertising your prefix from Google's network.</p><figure class="kg-card kg-image-card"><img src="https://sysadmin-journal.com/content/images/2026/02/announce.jpg" class="kg-image" alt="" loading="lazy" width="288" height="283"></figure><p>After announcing, your prefix will be advertised to the Internet from Google Cloud's infrastructure. You can now create Public Delegated Prefixes and start using your BYOIP addresses.</p><h3 id="creating-public-delegated-prefixes">Creating Public Delegated Prefixes</h3><p>While waiting for your PAP to provision, you can plan how to divide your address space. For a <code>/24</code> prefix, you might allocate addresses for:</p><ul><li>A <code>/26</code> for your primary regional workloads</li><li>A <code>/27</code> for global load balancers</li><li>A <code>/28</code> for development and testing</li><li>Reserve the remaining space for future use</li></ul><p>Each Public Delegated Prefix needs a scope assignment. Regional scope is required if you want to use addresses with Compute Engine VMs or regional load balancers. Global scope is needed for global Application Load Balancers but requires your project to be on an allowlist.</p><p>Oh... and about region selection. My usual trick for obtaining the best latency to a region is to understand how our submarine fiber cables connect us to the rest of the world. With the <code>africa-south1</code> region now available in Johannesburg, this is the obvious choice for organizations in Mauritius and across the AFRINIC region. The region is connected to Europe via the <a href="https://blog.google/intl/en-africa/company-news/inside-google/equianos-next-stop-is-in-nigeria/" rel="noreferrer">Equiano subsea cable</a> and to Mauritius via the SAFE, METISS and T3 subsea cables.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2026/02/mauritius-south-africa-submarine-cables.jpg" class="kg-image" alt="" loading="lazy" width="1055" height="752" srcset="/content/images/size/w600/2026/02/mauritius-south-africa-submarine-cables.jpg 600w, /content/images/size/w1000/2026/02/mauritius-south-africa-submarine-cables.jpg 1000w, /content/images/2026/02/mauritius-south-africa-submarine-cables.jpg 1055w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Map source: </span><a href="https://www2.telegeography.com/?__hstc=196094579.01e45c8d6f58c0cc4af8b54b80cc8fd8.1771274031194.1771274031194.1771274031194.1&amp;__hssc=196094579.1.1771274031194&amp;__hsfp=e9958329393ef73a8608d92d0cb7bf8d" target="_blank" rel="noopener noreferrer"><span style="white-space: pre-wrap;">TeleGeography</span></a></figcaption></figure><h3 id="delegating-sub-prefixes-to-projects">Delegating Sub-Prefixes to Projects</h3><p>Once your PDPs are provisioned, you can delegate sub-prefixes to specific projects within your organization. This is where BYOIP shines for enterprise environments—you can maintain centralized control over your IP address space while allowing individual teams to use addresses in their projects.</p><p>From the PDP details page, you can see all the information about your Public Delegated Prefix — the status (hopefully "Announced to Internet" ✅), the prefix details, scope, and any existing sub-delegates. Notice in the screenshot below that the scope is set to <code>africa-south1</code>.</p><figure class="kg-card kg-image-card"><img src="https://sysadmin-journal.com/content/images/2026/02/pdp-details.jpg" class="kg-image" alt="" loading="lazy" width="717" height="801" srcset="/content/images/size/w600/2026/02/pdp-details.jpg 600w, /content/images/2026/02/pdp-details.jpg 717w"></figure><p>To create a sub-prefix, click on "Create sub-prefix". You'll need to provide a name, select the prefix length (how many addresses you want to delegate), choose the specific IP range, and assign it to a project.</p><figure class="kg-card kg-image-card"><img src="https://sysadmin-journal.com/content/images/2026/02/create-delegation-1.jpg" class="kg-image" alt="" loading="lazy" width="842" height="611" srcset="/content/images/size/w600/2026/02/create-delegation-1.jpg 600w, /content/images/2026/02/create-delegation-1.jpg 842w" sizes="(min-width: 720px) 720px"></figure><p>In this example, I'm creating a <code>/26</code> sub-prefix (64 addresses) and assigning it to the "LSL IT" project. You can create multiple sub-prefixes of different sizes depending on your needs.</p><p>Once the sub-prefix is delegated to a project, you can create addresses from that pool. The "Create addresses" dialog lets you permanently delegate specific addresses to the project.</p><figure class="kg-card kg-image-card"><img src="https://sysadmin-journal.com/content/images/2026/02/create-addresses.jpg" class="kg-image" alt="" loading="lazy" width="572" height="368"></figure><p>Here I'm creating 2 addresses from a <code>/31</code> block. Once created, these addresses become available for use with Compute Engine instances, load balancers, and other Google Cloud resources within that project.</p><h3 id="using-byoip-addresses-with-compute-engine">Using BYOIP Addresses with Compute Engine</h3><p>With your addresses provisioned and delegated, using them with Compute Engine instances is straightforward. When creating a VM instance, navigate to the Networking section and expand the network interface settings. Under "External IPv4 address", you'll see your BYOIP addresses listed alongside the usual options.</p><figure class="kg-card kg-image-card"><img src="https://sysadmin-journal.com/content/images/2026/02/create-vm-instance.jpg" class="kg-image" alt="" loading="lazy" width="804" height="692" srcset="/content/images/size/w600/2026/02/create-vm-instance.jpg 600w, /content/images/2026/02/create-vm-instance.jpg 804w" sizes="(min-width: 720px) 720px"></figure><p>Notice in the screenshot that the BYOIP addresses (the <code>102.x.x.x</code> addresses) appear in the dropdown with "Premium tier" — these are your AFRINIC-allocated addresses now usable directly on Google Cloud! Also notice the machine is being created in <code>africa-south1-a</code> and running openSUSE Leap 16.0. 😉</p><p>The address behaves exactly like any Google-provided address — you can assign it to VM instances, use it with NAT gateways (with some limitations), and include it in firewall rules. The key differences are that your BYOIP addresses are available only to your organization, and there are no charges for idle or in-use addresses.</p>]]></content:encoded>
        </item>
        <item>
            <title>Google Cloud Workload Identity Federation: A Guide to Keyless Authentication for Multi-Cloud Environments</title>
            <link>https://sysadmin-journal.com/google-cloud-workload-identity-federation-a-guide-to-keyless-authentication-for-multi-cloud-environments</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/google-cloud-workload-identity-federation-a-guide-to-keyless-authentication-for-multi-cloud-environments</guid>
            <pubDate>Wed, 28 Jan 2026 16:58:07 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>Google Cloud Platform</category>
            <description>Learn how to eliminate service account keys and implement keyless authentication for GitHub Actions using Google Cloud Workload Identity Federation with Cloud SQL.</description>
            <content:encoded><![CDATA[
<!--kg-card-begin: html-->
<img src="https://sysadmin-journal.com/content/images/2026/01/workload-identity-federation-cover-2026.jpg" />
<!--kg-card-end: html-->
<h2 id="introduction">Introduction</h2><p>In today's multi-cloud landscape, managing authentication credentials across different cloud providers has become a significant security challenge. Traditional approaches using long-lived service account keys pose serious risks: they can be leaked, stolen, or compromised, and they're difficult to rotate and audit effectively.</p><p>Google Cloud Workload Identity Federation solves this problem by enabling keyless authentication. It allows workloads running outside Google Cloud—such as AWS EC2 instances, Azure VMs, GitHub Actions, or on-premises systems—to securely access Google Cloud resources without managing service account keys.</p><p>In this comprehensive guide, we'll walk through a real-world scenario and implement Workload Identity Federation step-by-step.</p><h2 id="the-scenario-multi-cloud-cicd-pipeline">The Scenario: Multi-Cloud CI/CD Pipeline</h2><p><strong>Company:</strong> TechFlow Inc., a fintech startup<br><strong>Challenge:</strong> Their application runs on Google Cloud (Cloud Run, Cloud SQL PostgreSQL), but their CI/CD pipelines run on GitHub Actions. They need to deploy applications and run database migrations on Cloud SQL from GitHub workflows without storing service account keys in GitHub secrets.</p><p><strong>Current Problem:</strong></p><ul><li>Service account keys stored in GitHub Secrets</li><li>Keys need manual rotation every 90 days</li><li>Risk of key exposure if repository is compromised</li><li>Compliance concerns about credential management</li><li>Database credentials management complexity</li></ul><p><strong>Goal:</strong> Implement Workload Identity Federation to enable GitHub Actions to authenticate to Google Cloud using OIDC tokens, eliminating the need for service account keys entirely.</p><h2 id="architecture-overview">Architecture Overview</h2><p>Here's how Workload Identity Federation works:</p><ol><li><strong>GitHub Actions</strong> generates an OIDC token containing claims about the workflow (repository, branch, actor)</li><li><strong>Workload Identity Pool</strong> in Google Cloud validates the token</li><li><strong>Workload Identity Provider</strong> maps the external identity to a Google Cloud service account</li><li>GitHub Actions receives short-lived Google Cloud credentials</li><li>Workflow uses credentials to access Google Cloud resources</li></ol><pre><code>┌─────────────────┐         ┌──────────────────────────┐
│  GitHub Actions │         │  Google Cloud            │
│                 │         │                          │
│  1. Get OIDC    │────────▶│  2. Workload Identity    │
│     Token       │         │     Pool validates token │
│                 │         │                          │
│  4. Receive     │◀────────│  3. Map to Service       │
│     GCP Token   │         │     Account              │
│                 │         │                          │
│  5. Access GCP  │────────▶│  6. Cloud Run, Cloud SQL │
│     Resources   │         │     (authorized access)  │
└─────────────────┘         └──────────────────────────┘
</code></pre><h2 id="prerequisites">Prerequisites</h2><p>Before we begin, ensure you have:</p><ol><li>A Google Cloud project with billing enabled</li><li><code>gcloud</code> CLI installed and configured</li><li>Appropriate IAM permissions:<ul><li><code>roles/iam.workloadIdentityPoolAdmin</code></li><li><code>roles/iam.serviceAccountAdmin</code></li><li><code>roles/resourcemanager.projectIamAdmin</code></li></ul></li><li>A GitHub repository where you'll run workflows</li></ol><h2 id="enable-required-apis">Enable Required APIs</h2><p>First, enable the necessary Google Cloud APIs:</p><pre><code class="language-bash"># Set your project ID
export PROJECT_ID="your-project-id"
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")

# Enable required APIs
gcloud services enable iamcredentials.googleapis.com \
    cloudresourcemanager.googleapis.com \
    sts.googleapis.com \
    --project=$PROJECT_ID
</code></pre><h2 id="create-a-service-account">Create a Service Account</h2><p>Create a service account that will be impersonated by GitHub Actions:</p><pre><code class="language-bash"># Create service account
gcloud iam service-accounts create github-actions-sa \
    --display-name="GitHub Actions Service Account" \
    --description="Service account for GitHub Actions workflows" \
    --project=$PROJECT_ID

# Store the service account email
export SA_EMAIL="github-actions-sa@${PROJECT_ID}.iam.gserviceaccount.com"
</code></pre><h2 id="grant-necessary-permissions-to-service-account">Grant Necessary Permissions to Service Account</h2><p>Grant the service account permissions to perform required operations. For our scenario, we need Cloud Run deployment and Cloud SQL access:</p><pre><code class="language-bash"># Cloud Run Admin (to deploy services)
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${SA_EMAIL}" \
    --role="roles/run.admin"

# Service Account User (to deploy as a service account)
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${SA_EMAIL}" \
    --role="roles/iam.serviceAccountUser"

# Cloud SQL Client (to connect to Cloud SQL instances)
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${SA_EMAIL}" \
    --role="roles/cloudsql.client"

# Cloud SQL Admin (to manage instances and run migrations)
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${SA_EMAIL}" \
    --role="roles/cloudsql.admin"

# Storage Admin (for artifact storage)
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${SA_EMAIL}" \
    --role="roles/storage.admin"

# Secret Manager Secret Accessor (to access database credentials)
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${SA_EMAIL}" \
    --role="roles/secretmanager.secretAccessor"
</code></pre><h2 id="create-workload-identity-pool">Create Workload Identity Pool</h2><p>The Workload Identity Pool is a container for managing external identities:</p><pre><code class="language-bash"># Create the workload identity pool
gcloud iam workload-identity-pools create "github-pool" \
    --location="global" \
    --display-name="GitHub Actions Pool" \
    --description="Identity pool for GitHub Actions workflows" \
    --project=$PROJECT_ID

# Verify creation
gcloud iam workload-identity-pools describe "github-pool" \
    --location="global" \
    --project=$PROJECT_ID
</code></pre><h2 id="create-workload-identity-provider">Create Workload Identity Provider</h2><p>The provider configures how external tokens are validated and mapped:</p><pre><code class="language-bash"># Create the OIDC provider for GitHub Actions
gcloud iam workload-identity-pools providers create-oidc "github-provider" \
    --location="global" \
    --workload-identity-pool="github-pool" \
    --display-name="GitHub Actions Provider" \
    --attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner" \
    --attribute-condition="assertion.repository_owner=='your-github-org'" \
    --issuer-uri="https://token.actions.githubusercontent.com" \
    --project=$PROJECT_ID
</code></pre><h3 id="understanding-attribute-mapping">Understanding Attribute Mapping</h3><p>The <code>--attribute-mapping</code> parameter is crucial. It maps claims from the GitHub OIDC token to Google Cloud attributes:</p><ul><li><code>google.subject=assertion.sub</code>: Maps the subject claim (unique identifier for the workflow)</li><li><code>attribute.actor=assertion.actor</code>: Maps the GitHub user who triggered the workflow</li><li><code>attribute.repository=assertion.repository</code>: Maps the repository name</li><li><code>attribute.repository_owner=assertion.repository_owner</code>: Maps the repository owner</li></ul><h3 id="understanding-attribute-conditions">Understanding Attribute Conditions</h3><p>The <code>--attribute-condition</code> parameter adds security by restricting which external identities can authenticate:</p><pre><code class="language-bash"># Only allow specific organization
assertion.repository_owner=='techflow-inc'

# Only allow specific repository
assertion.repository=='techflow-inc/main-app'

# Allow multiple repositories
assertion.repository in ['techflow-inc/app1', 'techflow-inc/app2']

# Combine conditions
assertion.repository_owner=='techflow-inc' &amp;&amp; assertion.repository.startsWith('techflow-inc/prod-')
</code></pre><p><strong>Important:</strong> Replace <code>'your-github-org'</code> with your actual GitHub organization or username.</p><h2 id="grant-service-account-impersonation-permission">Grant Service Account Impersonation Permission</h2><p>Allow the Workload Identity Pool to impersonate your service account. This is where you define precisely which external identities can impersonate the service account:</p><pre><code class="language-bash"># For a specific repository
gcloud iam service-accounts add-iam-policy-binding "${SA_EMAIL}" \
    --role="roles/iam.workloadIdentityUser" \
    --member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-pool/attribute.repository/your-github-username/your-repo-name" \
    --project=$PROJECT_ID
</code></pre><h3 id="advanced-iam-binding-examples">Advanced IAM Binding Examples</h3><p><strong>Option 1: Allow any repository in your organization</strong></p><pre><code class="language-bash">gcloud iam service-accounts add-iam-policy-binding "${SA_EMAIL}" \
    --role="roles/iam.workloadIdentityUser" \
    --member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-pool/attribute.repository_owner/your-github-org" \
    --project=$PROJECT_ID
</code></pre><p><strong>Option 2: Allow multiple specific repositories</strong></p><pre><code class="language-bash"># First repository
gcloud iam service-accounts add-iam-policy-binding "${SA_EMAIL}" \
    --role="roles/iam.workloadIdentityUser" \
    --member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-pool/attribute.repository/your-org/repo-1" \
    --project=$PROJECT_ID

# Second repository
gcloud iam service-accounts add-iam-policy-binding "${SA_EMAIL}" \
    --role="roles/iam.workloadIdentityUser" \
    --member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-pool/attribute.repository/your-org/repo-2" \
    --project=$PROJECT_ID
</code></pre><p><strong>Option 3: Allow specific branch in a repository</strong></p><pre><code class="language-bash"># This requires adding branch to attribute mapping first
gcloud iam workload-identity-pools providers update-oidc "github-provider" \
    --location="global" \
    --workload-identity-pool="github-pool" \
    --attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner,attribute.ref=assertion.ref" \
    --project=$PROJECT_ID

# Then bind for specific branch
gcloud iam service-accounts add-iam-policy-binding "${SA_EMAIL}" \
    --role="roles/iam.workloadIdentityUser" \
    --member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-pool/attribute.ref/refs/heads/main" \
    --project=$PROJECT_ID
</code></pre><h2 id="get-the-workload-identity-provider-resource-name">Get the Workload Identity Provider Resource Name</h2><p>You'll need this for your GitHub Actions workflow:</p><pre><code class="language-bash">gcloud iam workload-identity-pools providers describe "github-provider" \
    --location="global" \
    --workload-identity-pool="github-pool" \
    --project=$PROJECT_ID \
    --format="value(name)"
</code></pre><p>The output will look like:</p><pre><code>projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider
</code></pre><p>Save this value—you'll use it in your GitHub workflow.</p><h2 id="set-up-cloud-sql-instance-and-secrets">Set Up Cloud SQL Instance and Secrets</h2><p>Before configuring the workflow, set up your Cloud SQL instance and store credentials securely:</p><h3 id="create-cloud-sql-instance">Create Cloud SQL Instance</h3><pre><code class="language-bash"># Create a PostgreSQL instance
gcloud sql instances create production-db \
    --database-version=POSTGRES_15 \
    --tier=db-custom-2-7680 \
    --region=us-central1 \
    --network=projects/$PROJECT_ID/global/networks/default \
    --no-assign-ip \
    --database-flags=cloudsql.iam_authentication=on \
    --project=$PROJECT_ID

# Create a database
gcloud sql databases create production \
    --instance=production-db \
    --project=$PROJECT_ID

# Create a PostgreSQL user
gcloud sql users create dbuser \
    --instance=production-db \
    --password=STRONG_PASSWORD_HERE \
    --project=$PROJECT_ID
</code></pre><h3 id="store-database-password-in-secret-manager">Store Database Password in Secret Manager</h3><pre><code class="language-bash"># Enable Secret Manager API
gcloud services enable secretmanager.googleapis.com --project=$PROJECT_ID

# Create secret for database password
echo -n "STRONG_PASSWORD_HERE" | gcloud secrets create db-password \
    --data-file=- \
    --replication-policy="automatic" \
    --project=$PROJECT_ID

# Grant service account access to the secret
gcloud secrets add-iam-policy-binding db-password \
    --member="serviceAccount:${SA_EMAIL}" \
    --role="roles/secretmanager.secretAccessor" \
    --project=$PROJECT_ID
</code></pre><h3 id="alternative-use-iam-database-authentication-recommended">Alternative: Use IAM Database Authentication (Recommended)</h3><p>For enhanced security, use IAM authentication instead of passwords:</p><pre><code class="language-bash"># Create an IAM database user
gcloud sql users create github-actions-sa@$PROJECT_ID.iam \
    --instance=production-db \
    --type=CLOUD_IAM_SERVICE_ACCOUNT \
    --project=$PROJECT_ID

# Grant the service account Cloud SQL Client role
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${SA_EMAIL}" \
    --role="roles/cloudsql.client"
</code></pre><p>Then connect using IAM authentication in your workflow:</p><pre><code class="language-bash"># Get an IAM token for database authentication
gcloud sql generate-login-token

# Or use Cloud SQL Proxy which handles this automatically
./cloud_sql_proxy -instances=$INSTANCE_CONNECTION_NAME=tcp:5432 \
    --auto-iam-authn
</code></pre><h3 id="configure-cloud-sql-for-private-ip-best-practice">Configure Cloud SQL for Private IP (Best Practice)</h3><pre><code class="language-bash"># Enable Private Service Access
gcloud compute addresses create google-managed-services-default \
    --global \
    --purpose=VPC_PEERING \
    --prefix-length=16 \
    --network=default \
    --project=$PROJECT_ID

# Create private connection
gcloud services vpc-peerings connect \
    --service=servicenetworking.googleapis.com \
    --ranges=google-managed-services-default \
    --network=default \
    --project=$PROJECT_ID

# Create instance with private IP only
gcloud sql instances create production-db-private \
    --database-version=POSTGRES_15 \
    --tier=db-custom-2-7680 \
    --region=us-central1 \
    --network=projects/$PROJECT_ID/global/networks/default \
    --no-assign-ip \
    --database-flags=cloudsql.iam_authentication=on \
    --project=$PROJECT_ID
</code></pre><h2 id="configure-github-actions-workflow">Configure GitHub Actions Workflow</h2><p>Now, create a GitHub Actions workflow that uses Workload Identity Federation:</p><pre><code class="language-yaml">name: Deploy to Cloud Run and Run Migrations

on:
  push:
    branches:
      - main

# Required for OIDC token generation
permissions:
  contents: read
  id-token: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      # Authenticate to Google Cloud using Workload Identity Federation
      - id: 'auth'
        name: 'Authenticate to Google Cloud'
        uses: 'google-github-actions/auth@v2'
        with:
          workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider'
          service_account: 'github-actions-sa@your-project-id.iam.gserviceaccount.com'
          token_format: 'access_token'

      # Setup Cloud SDK
      - name: 'Set up Cloud SDK'
        uses: 'google-github-actions/setup-gcloud@v2'

      # Install Cloud SQL Proxy
      - name: 'Install Cloud SQL Proxy'
        run: |
          wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 -O cloud_sql_proxy
          chmod +x cloud_sql_proxy

      # Build and push Docker image
      - name: 'Build and Push to Artifact Registry'
        run: |
          gcloud auth configure-docker us-central1-docker.pkg.dev
          docker build -t us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/apps/myapp:${{ github.sha }} .
          docker push us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/apps/myapp:${{ github.sha }}

      # Run database migrations using Cloud SQL Proxy
      - name: 'Run Database Migrations'
        env:
          INSTANCE_CONNECTION_NAME: ${{ secrets.GCP_PROJECT_ID }}:us-central1:production-db
        run: |
          # Start Cloud SQL Proxy in background
          ./cloud_sql_proxy -instances=$INSTANCE_CONNECTION_NAME=tcp:5432 &amp;
          PROXY_PID=$!
          
          # Wait for proxy to be ready
          sleep 5
          
          # Get database password from Secret Manager
          DB_PASSWORD=$(gcloud secrets versions access latest --secret="db-password")
          
          # Run migrations (example using Flyway)
          export PGPASSWORD=$DB_PASSWORD
          psql -h 127.0.0.1 -p 5432 -U dbuser -d production -f migrations/001_initial_schema.sql
          
          # Or using a migration tool like golang-migrate
          # migrate -path=./migrations -database "postgresql://dbuser:${DB_PASSWORD}@127.0.0.1:5432/production?sslmode=disable" up
          
          # Kill the proxy
          kill $PROXY_PID

      # Deploy to Cloud Run with Cloud SQL connection
      - name: 'Deploy to Cloud Run'
        run: |
          gcloud run deploy myapp \
            --image=us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/apps/myapp:${{ github.sha }} \
            --region=us-central1 \
            --platform=managed \
            --allow-unauthenticated \
            --add-cloudsql-instances=${{ secrets.GCP_PROJECT_ID }}:us-central1:production-db \
            --set-env-vars="DB_USER=dbuser,DB_NAME=production" \
            --set-secrets="DB_PASSWORD=db-password:latest"

      # Verify deployment and database connectivity
      - name: 'Health Check'
        run: |
          # Get the service URL
          SERVICE_URL=$(gcloud run services describe myapp --region=us-central1 --format='value(status.url)')
          
          # Check health endpoint
          curl -f ${SERVICE_URL}/health || exit 1
          
          # Verify database connection through the app
          curl -f ${SERVICE_URL}/db/ping || exit 1
</code></pre><h3 id="key-workflow-elements">Key Workflow Elements</h3><ol><li><strong>Permissions Block</strong>: The <code>id-token: write</code> permission is crucial—it allows GitHub to generate OIDC tokens.</li><li><strong>Auth Action</strong>: The <code>google-github-actions/auth@v2</code> action handles the token exchange automatically.</li><li><strong>No Secrets Required</strong>: Notice we don't store any service account keys in GitHub Secrets, only the project ID.</li><li><strong>Cloud SQL Proxy</strong>: The proxy creates a secure tunnel to Cloud SQL, handling authentication via the service account.</li><li><strong>Secret Manager Integration</strong>: Database passwords are stored securely and accessed at runtime.</li></ol><h2 id="advanced-cloud-sql-workflow-patterns">Advanced Cloud SQL Workflow Patterns</h2><h3 id="pattern-1-using-iam-authentication-most-secure">Pattern 1: Using IAM Authentication (Most Secure)</h3><pre><code class="language-yaml">name: Deploy with IAM Database Authentication

on:
  push:
    branches:
      - main

permissions:
  contents: read
  id-token: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4

      - id: 'auth'
        uses: 'google-github-actions/auth@v2'
        with:
          workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider'
          service_account: 'github-actions-sa@your-project-id.iam.gserviceaccount.com'

      - uses: 'google-github-actions/setup-gcloud@v2'

      # Use Cloud SQL Auth Proxy with IAM authentication
      - name: 'Run Migrations with IAM Auth'
        run: |
          # Install Cloud SQL Auth Proxy
          curl -o cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.8.0/cloud-sql-proxy.linux.amd64
          chmod +x cloud-sql-proxy
          
          # Start proxy with automatic IAM authentication
          ./cloud-sql-proxy --auto-iam-authn ${{ secrets.GCP_PROJECT_ID }}:us-central1:production-db &amp;
          PROXY_PID=$!
          sleep 5
          
          # Connect using IAM - no password needed!
          psql "host=127.0.0.1 port=5432 sslmode=disable user=github-actions-sa@${{ secrets.GCP_PROJECT_ID }}.iam dbname=production" \
            -c "SELECT version();"
          
          # Run migrations
          psql "host=127.0.0.1 port=5432 sslmode=disable user=github-actions-sa@${{ secrets.GCP_PROJECT_ID }}.iam dbname=production" \
            -f migrations/schema.sql
          
          kill $PROXY_PID
</code></pre><h3 id="pattern-2-using-flyway-for-database-migrations">Pattern 2: Using Flyway for Database Migrations</h3><pre><code class="language-yaml">      - name: 'Run Flyway Migrations'
        env:
          INSTANCE_CONNECTION_NAME: ${{ secrets.GCP_PROJECT_ID }}:us-central1:production-db
        run: |
          # Start Cloud SQL Proxy
          ./cloud_sql_proxy -instances=$INSTANCE_CONNECTION_NAME=tcp:5432 &amp;
          PROXY_PID=$!
          sleep 5
          
          # Get database password
          DB_PASSWORD=$(gcloud secrets versions access latest --secret="db-password")
          
          # Install Flyway
          wget -qO- https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/10.4.1/flyway-commandline-10.4.1-linux-x64.tar.gz | tar xvz
          
          # Configure Flyway
          ./flyway-10.4.1/flyway \
            -url=jdbc:postgresql://127.0.0.1:5432/production \
            -user=dbuser \
            -password=$DB_PASSWORD \
            -locations=filesystem:./sql/migrations \
            migrate
          
          # Verify migration status
          ./flyway-10.4.1/flyway \
            -url=jdbc:postgresql://127.0.0.1:5432/production \
            -user=dbuser \
            -password=$DB_PASSWORD \
            info
          
          kill $PROXY_PID
</code></pre><h3 id="pattern-3-blue-green-database-migrations-with-rollback">Pattern 3: Blue-Green Database Migrations with Rollback</h3><pre><code class="language-yaml">      - name: 'Blue-Green Migration with Rollback'
        run: |
          # Start proxy
          ./cloud_sql_proxy -instances=${{ secrets.GCP_PROJECT_ID }}:us-central1:production-db=tcp:5432 &amp;
          PROXY_PID=$!
          sleep 5
          
          DB_PASSWORD=$(gcloud secrets versions access latest --secret="db-password")
          export PGPASSWORD=$DB_PASSWORD
          
          # Create backup before migration
          pg_dump -h 127.0.0.1 -p 5432 -U dbuser production &gt; backup_$(date +%Y%m%d_%H%M%S).sql
          
          # Upload backup to Cloud Storage
          gcloud storage cp backup_*.sql gs://${{ secrets.GCP_PROJECT_ID }}-db-backups/
          
          # Create test database from production
          psql -h 127.0.0.1 -p 5432 -U dbuser -d postgres \
            -c "CREATE DATABASE production_test WITH TEMPLATE production;"
          
          # Test migrations on copy
          if psql -h 127.0.0.1 -p 5432 -U dbuser -d production_test -f migrations/schema.sql; then
            echo "✓ Migration test successful"
            
            # Apply to production
            psql -h 127.0.0.1 -p 5432 -U dbuser -d production -f migrations/schema.sql
            
            # Verify production migration
            if psql -h 127.0.0.1 -p 5432 -U dbuser -d production -c "SELECT COUNT(*) FROM users;" &gt; /dev/null; then
              echo "✓ Production migration successful"
            else
              echo "✗ Production migration failed, restoring backup"
              psql -h 127.0.0.1 -p 5432 -U dbuser -d production &lt; backup_*.sql
              exit 1
            fi
            
            # Cleanup test database
            psql -h 127.0.0.1 -p 5432 -U dbuser -d postgres \
              -c "DROP DATABASE production_test;"
          else
            echo "✗ Migration test failed, aborting"
            exit 1
          fi
          
          kill $PROXY_PID
</code></pre><h3 id="pattern-4-multi-region-database-deployment">Pattern 4: Multi-Region Database Deployment</h3><pre><code class="language-yaml">jobs:
  deploy-multi-region:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        region: 
          - name: us-central1
            db: production-db-us
          - name: europe-west1
            db: production-db-eu
          - name: asia-southeast1
            db: production-db-asia
    
    steps:
      - uses: actions/checkout@v4
      
      - id: 'auth'
        uses: 'google-github-actions/auth@v2'
        with:
          workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider'
          service_account: 'github-actions-sa@your-project-id.iam.gserviceaccount.com'
      
      - uses: 'google-github-actions/setup-gcloud@v2'
      
      - name: 'Deploy to ${{ matrix.region.name }}'
        run: |
          # Install Cloud SQL Proxy
          curl -o cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.8.0/cloud-sql-proxy.linux.amd64
          chmod +x cloud-sql-proxy
          
          # Start proxy for this region
          ./cloud-sql-proxy ${{ secrets.GCP_PROJECT_ID }}:${{ matrix.region.name }}:${{ matrix.region.db }} &amp;
          PROXY_PID=$!
          sleep 5
          
          # Get region-specific password
          DB_PASSWORD=$(gcloud secrets versions access latest --secret="db-password-${{ matrix.region.name }}")
          export PGPASSWORD=$DB_PASSWORD
          
          # Run common migrations
          psql -h 127.0.0.1 -p 5432 -U dbuser -d production \
            -f migrations/common/schema.sql
          
          # Run region-specific configurations
          psql -h 127.0.0.1 -p 5432 -U dbuser -d production \
            -v region=${{ matrix.region.name }} \
            -f migrations/regional/config.sql
          
          kill $PROXY_PID
          
          # Deploy Cloud Run in this region
          gcloud run deploy myapp-${{ matrix.region.name }} \
            --image=us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/apps/myapp:${{ github.sha }} \
            --region=${{ matrix.region.name }} \
            --add-cloudsql-instances=${{ secrets.GCP_PROJECT_ID }}:${{ matrix.region.name }}:${{ matrix.region.db }} \
            --set-env-vars="REGION=${{ matrix.region.name }},DB_NAME=production"
</code></pre><h2 id="advanced-configurationmultiple-environments">Advanced Configuration - Multiple Environments</h2><p>For production deployments, you'll likely need different service accounts for different environments:</p><pre><code class="language-bash"># Create production service account
gcloud iam service-accounts create github-actions-prod-sa \
    --display-name="GitHub Actions Production SA" \
    --project=$PROJECT_ID

export PROD_SA_EMAIL="github-actions-prod-sa@${PROJECT_ID}.iam.gserviceaccount.com"

# Bind only to main branch
gcloud iam service-accounts add-iam-policy-binding "${PROD_SA_EMAIL}" \
    --role="roles/iam.workloadIdentityUser" \
    --member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-pool/attribute.ref/refs/heads/main" \
    --project=$PROJECT_ID

# Create staging service account
gcloud iam service-accounts create github-actions-staging-sa \
    --display-name="GitHub Actions Staging SA" \
    --project=$PROJECT_ID

export STAGING_SA_EMAIL="github-actions-staging-sa@${PROJECT_ID}.iam.gserviceaccount.com"

# Bind to develop branch
gcloud iam service-accounts add-iam-policy-binding "${STAGING_SA_EMAIL}" \
    --role="roles/iam.workloadIdentityUser" \
    --member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-pool/attribute.ref/refs/heads/develop" \
    --project=$PROJECT_ID
</code></pre><p>Then in your workflow:</p><pre><code class="language-yaml">- id: 'auth'
  name: 'Authenticate to Google Cloud'
  uses: 'google-github-actions/auth@v2'
  with:
    workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider'
    service_account: ${{ github.ref == 'refs/heads/main' &amp;&amp; 'github-actions-prod-sa@project.iam.gserviceaccount.com' || 'github-actions-staging-sa@project.iam.gserviceaccount.com' }}
</code></pre><h2 id="verification-and-testing">Verification and Testing</h2><h3 id="test-1-verify-token-exchange">Test 1: Verify Token Exchange</h3><p>Create a simple test workflow:</p><pre><code class="language-yaml">name: Test Workload Identity

on:
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - id: 'auth'
        uses: 'google-github-actions/auth@v2'
        with:
          workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider'
          service_account: 'github-actions-sa@your-project-id.iam.gserviceaccount.com'
      
      - name: 'Verify Authentication'
        run: |
          gcloud auth list
          gcloud projects describe ${{ secrets.GCP_PROJECT_ID }}
</code></pre><h3 id="test-2-audit-logs">Test 2: Audit Logs</h3><p>Check Cloud Audit Logs to verify authentication:</p><pre><code class="language-bash">gcloud logging read "protoPayload.methodName=GenerateAccessToken" \
    --limit=10 \
    --format=json \
    --project=$PROJECT_ID
</code></pre><p>Look for entries showing GitHub Actions successfully exchanging tokens.</p><h2 id="security-best-practices">Security Best Practices</h2><h3 id="1-principle-of-least-privilege">1. Principle of Least Privilege</h3><p>Grant only the minimum permissions needed:</p><pre><code class="language-bash"># Instead of broad roles, use specific permissions
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${SA_EMAIL}" \
    --role="roles/run.developer"  # Instead of run.admin

gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${SA_EMAIL}" \
    --role="roles/cloudsql.client"  # Instead of cloudsql.admin for read-only access
</code></pre><h3 id="2-use-attribute-conditions-strictly">2. Use Attribute Conditions Strictly</h3><p>Always restrict which external identities can authenticate:</p><pre><code class="language-bash"># Bad: Too permissive
--attribute-condition=""

# Good: Specific organization
--attribute-condition="assertion.repository_owner=='your-org'"

# Better: Specific repositories and branches
--attribute-condition="assertion.repository_owner=='your-org' &amp;&amp; assertion.repository.startsWith('your-org/prod-') &amp;&amp; assertion.ref=='refs/heads/main'"
</code></pre><h3 id="3-separate-service-accounts-by-environment">3. Separate Service Accounts by Environment</h3><p>Never use the same service account for production and staging:</p><pre><code>✗ github-actions-sa → All environments
✓ github-actions-prod-sa → Production only
✓ github-actions-staging-sa → Staging only
✓ github-actions-dev-sa → Development only
</code></pre><h3 id="4-enable-audit-logging">4. Enable Audit Logging</h3><p>Ensure Data Access audit logs are enabled:</p><pre><code class="language-bash"># Create audit config
cat &gt; audit-config.yaml &lt;&lt;EOF
auditConfigs:
- auditLogConfigs:
  - logType: ADMIN_READ
  - logType: DATA_READ
  - logType: DATA_WRITE
  service: iam.googleapis.com
EOF

gcloud projects set-iam-policy $PROJECT_ID audit-config.yaml
</code></pre><h3 id="5-monitor-token-exchange-activity">5. Monitor Token Exchange Activity</h3><p>Set up monitoring alerts for suspicious activity:</p><pre><code class="language-bash"># Create a log-based metric
gcloud logging metrics create workload-identity-failures \
    --description="Failed workload identity token exchanges" \
    --log-filter='protoPayload.methodName="GenerateAccessToken"
    AND protoPayload.status.code!=0'

# Create alert policy
gcloud alpha monitoring policies create \
    --notification-channels=CHANNEL_ID \
    --display-name="Workload Identity Failures" \
    --condition-display-name="High failure rate" \
    --condition-threshold-value=5 \
    --condition-threshold-duration=60s
</code></pre><h3 id="6-cloud-sql-security-best-practices">6. Cloud SQL Security Best Practices</h3><p>Implement these security measures for Cloud SQL:</p><pre><code class="language-bash"># Enable automatic backups
gcloud sql instances patch production-db \
    --backup-start-time=03:00 \
    --enable-bin-log \
    --project=$PROJECT_ID

# Enable point-in-time recovery
gcloud sql instances patch production-db \
    --enable-point-in-time-recovery \
    --project=$PROJECT_ID

# Require SSL for all connections
gcloud sql instances patch production-db \
    --require-ssl \
    --project=$PROJECT_ID

# Enable database flags for security
gcloud sql instances patch production-db \
    --database-flags=\
cloudsql.iam_authentication=on,\
log_checkpoints=on,\
log_connections=on,\
log_disconnections=on,\
log_lock_waits=on,\
log_statement=ddl,\
log_min_duration_statement=1000 \
    --project=$PROJECT_ID

# Restrict network access (private IP only)
gcloud sql instances patch production-db \
    --no-assign-ip \
    --network=projects/$PROJECT_ID/global/networks/default \
    --project=$PROJECT_ID
</code></pre><p><strong>Database User Security:</strong></p><pre><code class="language-sql">-- Revoke public schema privileges
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
REVOKE ALL ON DATABASE production FROM PUBLIC;

-- Create read-only role for analytics
CREATE ROLE analytics_readonly;
GRANT CONNECT ON DATABASE production TO analytics_readonly;
GRANT USAGE ON SCHEMA public TO analytics_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO analytics_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO analytics_readonly;

-- Create application role with limited privileges
CREATE ROLE app_user;
GRANT CONNECT ON DATABASE production TO app_user;
GRANT USAGE ON SCHEMA public TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_user;

-- Assign IAM service account to role
GRANT app_user TO "github-actions-sa@project-id.iam";
</code></pre><p><strong>Automated Security Scanning:</strong></p><pre><code class="language-yaml"># Add to your GitHub workflow
- name: 'Scan Database for Security Issues'
  run: |
    # Check for overly permissive grants
    psql -h 127.0.0.1 -p 5432 -U dbuser -d production &lt;&lt;EOF
    SELECT 
      grantee, 
      string_agg(privilege_type, ', ') as privileges,
      table_schema,
      table_name
    FROM information_schema.table_privileges
    WHERE grantee = 'PUBLIC'
    GROUP BY grantee, table_schema, table_name;
    EOF
    
    # Check for weak passwords (if not using IAM auth)
    psql -h 127.0.0.1 -p 5432 -U dbuser -d production &lt;&lt;EOF
    SELECT usename 
    FROM pg_shadow 
    WHERE passwd IS NULL OR passwd = '';
    EOF
</code></pre><h3 id="7-secrets-rotation-strategy">7. Secrets Rotation Strategy</h3><p>Automate credential rotation for enhanced security:</p><pre><code class="language-bash"># Create a rotation script
cat &gt; rotate-db-password.sh &lt;&lt;'EOF'
#!/bin/bash
set -e

PROJECT_ID=$1
INSTANCE_NAME=$2
DB_USER=$3
SECRET_NAME=$4

# Generate new password
NEW_PASSWORD=$(openssl rand -base64 32)

# Update Cloud SQL user
gcloud sql users set-password $DB_USER \
    --instance=$INSTANCE_NAME \
    --password=$NEW_PASSWORD \
    --project=$PROJECT_ID

# Update Secret Manager
echo -n "$NEW_PASSWORD" | gcloud secrets versions add $SECRET_NAME \
    --data-file=- \
    --project=$PROJECT_ID

# Disable old secret versions (after grace period)
OLD_VERSIONS=$(gcloud secrets versions list $SECRET_NAME \
    --filter="state:ENABLED" \
    --format="value(name)" \
    --sort-by="~createTime" \
    --limit=5 \
    | tail -n +3)

for VERSION in $OLD_VERSIONS; do
    gcloud secrets versions disable $VERSION --secret=$SECRET_NAME --project=$PROJECT_ID
done

echo "Password rotated successfully"
EOF

chmod +x rotate-db-password.sh

# Schedule rotation with Cloud Scheduler
gcloud scheduler jobs create http db-password-rotation \
    --schedule="0 0 1 * *" \
    --uri="https://your-cloud-function-url/rotate-password" \
    --http-method=POST \
    --oidc-service-account-email=$SA_EMAIL \
    --project=$PROJECT_ID
</code></pre><h2 id="troubleshooting-common-issues">Troubleshooting Common Issues</h2><h3 id="issue-1-permission-denied-when-exchanging-token">Issue 1: "Permission denied" when exchanging token</h3><p><strong>Symptom:</strong></p><pre><code>Error: google-github-actions/auth failed with: failed to generate Google Cloud access token: 
(400) {"error":"invalid_target","error_description":"The provided target service account is invalid"}
</code></pre><p><strong>Solution:</strong> Verify the IAM binding:</p><pre><code class="language-bash">gcloud iam service-accounts get-iam-policy $SA_EMAIL \
    --project=$PROJECT_ID \
    --flatten="bindings[].members" \
    --filter="bindings.role:roles/iam.workloadIdentityUser"
</code></pre><p>Ensure the principal matches your repository exactly.</p><h3 id="issue-2-attribute-condition-not-satisfied">Issue 2: "Attribute condition not satisfied"</h3><p><strong>Symptom:</strong></p><pre><code>Error: (403) Attribute condition not satisfied
</code></pre><p><strong>Solution:</strong> Check your attribute condition matches the token claims:</p><pre><code class="language-bash"># Update condition to be more permissive temporarily for debugging
gcloud iam workload-identity-pools providers update-oidc "github-provider" \
    --location="global" \
    --workload-identity-pool="github-pool" \
    --attribute-condition="" \
    --project=$PROJECT_ID
</code></pre><p>Then examine the actual token claims in audit logs and adjust your condition.</p><h3 id="issue-3-missing-id-token-write-permission">Issue 3: Missing <code>id-token: write</code> permission</h3><p><strong>Symptom:</strong></p><pre><code>Error: Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable
</code></pre><p><strong>Solution:</strong> Add to your workflow:</p><pre><code class="language-yaml">permissions:
  id-token: write
  contents: read
</code></pre><h3 id="issue-4-insufficient-permissions-for-service-account">Issue 4: Insufficient permissions for service account</h3><p><strong>Symptom:</strong></p><pre><code>Error: (403) Permission denied on resource
</code></pre><p><strong>Solution:</strong> Review and grant required roles:</p><pre><code class="language-bash"># Check current roles
gcloud projects get-iam-policy $PROJECT_ID \
    --flatten="bindings[].members" \
    --filter="bindings.members:serviceAccount:${SA_EMAIL}"

# Add missing role
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${SA_EMAIL}" \
    --role="roles/REQUIRED_ROLE"
</code></pre><h3 id="issue-5-cloud-sql-proxy-connection-timeout">Issue 5: Cloud SQL Proxy Connection Timeout</h3><p><strong>Symptom:</strong></p><pre><code>Error: couldn't connect to &lt;instance&gt;: dial tcp 127.0.0.1:5432: connect: connection refused
</code></pre><p><strong>Solution:</strong> Ensure the proxy has started and the service account has <code>cloudsql.client</code> role:</p><pre><code class="language-bash"># Verify service account has Cloud SQL Client role
gcloud projects get-iam-policy $PROJECT_ID \
    --flatten="bindings[].members" \
    --filter="bindings.members:serviceAccount:${SA_EMAIL}" \
    --filter="bindings.role:roles/cloudsql.client"

# Grant if missing
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${SA_EMAIL}" \
    --role="roles/cloudsql.client"

# In your workflow, add more wait time
./cloud_sql_proxy -instances=$INSTANCE &amp;
sleep 10  # Increase from 5 to 10 seconds

# Or check proxy logs
./cloud_sql_proxy -instances=$INSTANCE -verbose &amp;
</code></pre><h3 id="issue-6-iam-database-authentication-failures">Issue 6: IAM Database Authentication Failures</h3><p><strong>Symptom:</strong></p><pre><code>psql: error: connection to server at "127.0.0.1", port 5432 failed: 
FATAL: password authentication failed for user "github-actions-sa@project.iam"
</code></pre><p><strong>Solution:</strong> Verify IAM user exists and has proper grants:</p><pre><code class="language-bash"># Check if IAM user exists
gcloud sql users list --instance=production-db --project=$PROJECT_ID

# Create IAM user if missing
gcloud sql users create github-actions-sa@$PROJECT_ID.iam \
    --instance=production-db \
    --type=CLOUD_IAM_SERVICE_ACCOUNT \
    --project=$PROJECT_ID

# Grant database permissions (connect via Cloud SQL Proxy first)
psql "host=127.0.0.1 port=5432 user=postgres dbname=production" &lt;&lt;EOF
GRANT CONNECT ON DATABASE production TO "github-actions-sa@$PROJECT_ID.iam";
GRANT USAGE ON SCHEMA public TO "github-actions-sa@$PROJECT_ID.iam";
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO "github-actions-sa@$PROJECT_ID.iam";
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO "github-actions-sa@$PROJECT_ID.iam";
EOF
</code></pre><h3 id="issue-7-secret-manager-access-denied">Issue 7: Secret Manager Access Denied</h3><p><strong>Symptom:</strong></p><pre><code>Error: gcloud secrets versions access latest --secret="db-password"
ERROR: (gcloud.secrets.versions.access) PERMISSION_DENIED
</code></pre><p><strong>Solution:</strong> Grant Secret Manager access:</p><pre><code class="language-bash"># Grant access to specific secret
gcloud secrets add-iam-policy-binding db-password \
    --member="serviceAccount:${SA_EMAIL}" \
    --role="roles/secretmanager.secretAccessor" \
    --project=$PROJECT_ID

# Or grant project-wide access (less secure)
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:${SA_EMAIL}" \
    --role="roles/secretmanager.secretAccessor"
</code></pre><h3 id="issue-8-cloud-sql-instance-not-found">Issue 8: Cloud SQL Instance Not Found</h3><p><strong>Symptom:</strong></p><pre><code>Error: instance does not exist or you are not authorized to access it
</code></pre><p><strong>Solution:</strong> Verify instance name and permissions:</p><pre><code class="language-bash"># List all Cloud SQL instances
gcloud sql instances list --project=$PROJECT_ID

# Describe specific instance
gcloud sql instances describe production-db --project=$PROJECT_ID

# Ensure correct connection name format: PROJECT_ID:REGION:INSTANCE_NAME
# Example: my-project:us-central1:production-db
</code></pre><h3 id="issue-9-migration-script-fails-silently">Issue 9: Migration Script Fails Silently</h3><p><strong>Symptom:</strong> Workflow succeeds but migrations don't apply.</p><p><strong>Solution:</strong> Add explicit error handling:</p><pre><code class="language-yaml">- name: 'Run Migrations with Error Handling'
  run: |
    set -e  # Exit on any error
    set -o pipefail  # Catch errors in pipes
    
    ./cloud_sql_proxy -instances=$INSTANCE_CONNECTION_NAME=tcp:5432 &amp;
    PROXY_PID=$!
    
    # Ensure proxy cleanup on exit
    trap "kill $PROXY_PID 2&gt;/dev/null || true" EXIT
    
    sleep 5
    
    DB_PASSWORD=$(gcloud secrets versions access latest --secret="db-password")
    export PGPASSWORD=$DB_PASSWORD
    
    # Run migration with verbose output
    psql -h 127.0.0.1 -p 5432 -U dbuser -d production \
      -v ON_ERROR_STOP=1 \
      -f migrations/schema.sql \
      2&gt;&amp;1 | tee migration.log
    
    # Check exit code explicitly
    if [ ${PIPESTATUS[0]} -ne 0 ]; then
      echo "Migration failed!"
      cat migration.log
      exit 1
    fi
</code></pre><h3 id="issue-10-connection-pool-exhaustion">Issue 10: Connection Pool Exhaustion</h3><p><strong>Symptom:</strong></p><pre><code>Error: remaining connection slots are reserved for non-replication superuser connections
</code></pre><p><strong>Solution:</strong> Implement connection pooling and limits:</p><pre><code class="language-yaml">- name: 'Use Connection Pooling'
  run: |
    # Increase max_connections on Cloud SQL instance
    gcloud sql instances patch production-db \
      --database-flags=max_connections=100 \
      --project=$PROJECT_ID
    
    # Or use PgBouncer for connection pooling
    cat &gt; pgbouncer.ini &lt;&lt;EOF
[databases]
production = host=127.0.0.1 port=5432 dbname=production

[pgbouncer]
pool_mode = transaction
max_client_conn = 100
default_pool_size = 20
reserve_pool_size = 5
EOF
    
    pgbouncer -d pgbouncer.ini
    
    # Connect through PgBouncer on port 6432
    psql -h 127.0.0.1 -p 6432 -U dbuser -d production -f migrations/schema.sql
</code></pre><h2 id="advanced-topics">Advanced Topics</h2><h3 id="complete-infrastructure-provisioning-with-gcloud">Complete Infrastructure Provisioning with gcloud</h3><p>Here's a complete script to provision all infrastructure using gcloud commands:</p><pre><code class="language-bash">#!/bin/bash
# complete-setup.sh - Complete Workload Identity Federation setup script

set -e  # Exit on error

# Configuration variables
export PROJECT_ID="your-project-id"
export GITHUB_ORG="your-github-org"
export GITHUB_REPO="your-repo-name"
export REGION="us-central1"
export DB_INSTANCE_NAME="production-db"
export DB_NAME="production"
export DB_USER="dbuser"

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

echo -e "${GREEN}Starting Workload Identity Federation setup...${NC}"

# Step 1: Enable required APIs
echo -e "${YELLOW}Enabling required APIs...${NC}"
gcloud services enable \
    iamcredentials.googleapis.com \
    cloudresourcemanager.googleapis.com \
    sts.googleapis.com \
    sqladmin.googleapis.com \
    secretmanager.googleapis.com \
    run.googleapis.com \
    cloudbuild.googleapis.com \
    artifactregistry.googleapis.com \
    --project=$PROJECT_ID

# Get project number
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")
echo -e "${GREEN}Project Number: $PROJECT_NUMBER${NC}"

# Step 2: Create Workload Identity Pool
echo -e "${YELLOW}Creating Workload Identity Pool...${NC}"
if gcloud iam workload-identity-pools describe "github-pool" \
    --location="global" \
    --project=$PROJECT_ID &amp;&gt;/dev/null; then
    echo -e "${GREEN}Pool already exists, skipping...${NC}"
else
    gcloud iam workload-identity-pools create "github-pool" \
        --location="global" \
        --display-name="GitHub Actions Pool" \
        --description="Identity pool for GitHub Actions workflows" \
        --project=$PROJECT_ID
    echo -e "${GREEN}✓ Pool created${NC}"
fi

# Step 3: Create Workload Identity Provider
echo -e "${YELLOW}Creating Workload Identity Provider...${NC}"
if gcloud iam workload-identity-pools providers describe "github-provider" \
    --location="global" \
    --workload-identity-pool="github-pool" \
    --project=$PROJECT_ID &amp;&gt;/dev/null; then
    echo -e "${GREEN}Provider already exists, skipping...${NC}"
else
    gcloud iam workload-identity-pools providers create-oidc "github-provider" \
        --location="global" \
        --workload-identity-pool="github-pool" \
        --display-name="GitHub Actions Provider" \
        --attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner,attribute.ref=assertion.ref" \
        --attribute-condition="assertion.repository_owner=='${GITHUB_ORG}'" \
        --issuer-uri="https://token.actions.githubusercontent.com" \
        --project=$PROJECT_ID
    echo -e "${GREEN}✓ Provider created${NC}"
fi

# Step 4: Create Service Accounts
echo -e "${YELLOW}Creating service accounts...${NC}"

# Production service account
if gcloud iam service-accounts describe github-actions-prod-sa@${PROJECT_ID}.iam.gserviceaccount.com \
    --project=$PROJECT_ID &amp;&gt;/dev/null; then
    echo -e "${GREEN}Production SA already exists, skipping...${NC}"
else
    gcloud iam service-accounts create github-actions-prod-sa \
        --display-name="GitHub Actions Production SA" \
        --description="Production deployment service account" \
        --project=$PROJECT_ID
    echo -e "${GREEN}✓ Production SA created${NC}"
fi

# Staging service account
if gcloud iam service-accounts describe github-actions-staging-sa@${PROJECT_ID}.iam.gserviceaccount.com \
    --project=$PROJECT_ID &amp;&gt;/dev/null; then
    echo -e "${GREEN}Staging SA already exists, skipping...${NC}"
else
    gcloud iam service-accounts create github-actions-staging-sa \
        --display-name="GitHub Actions Staging SA" \
        --description="Staging deployment service account" \
        --project=$PROJECT_ID
    echo -e "${GREEN}✓ Staging SA created${NC}"
fi

export PROD_SA_EMAIL="github-actions-prod-sa@${PROJECT_ID}.iam.gserviceaccount.com"
export STAGING_SA_EMAIL="github-actions-staging-sa@${PROJECT_ID}.iam.gserviceaccount.com"

# Step 5: Grant IAM permissions to service accounts
echo -e "${YELLOW}Granting IAM permissions...${NC}"

# Production permissions
for ROLE in "roles/run.admin" "roles/iam.serviceAccountUser" "roles/cloudsql.client" \
            "roles/storage.admin" "roles/secretmanager.secretAccessor"; do
    gcloud projects add-iam-policy-binding $PROJECT_ID \
        --member="serviceAccount:${PROD_SA_EMAIL}" \
        --role="$ROLE" \
        --condition=None \
        --quiet
done

# Staging permissions (more limited)
for ROLE in "roles/run.developer" "roles/cloudsql.client" \
            "roles/secretmanager.secretAccessor"; do
    gcloud projects add-iam-policy-binding $PROJECT_ID \
        --member="serviceAccount:${STAGING_SA_EMAIL}" \
        --role="$ROLE" \
        --condition=None \
        --quiet
done

echo -e "${GREEN}✓ IAM permissions granted${NC}"

# Step 6: Bind workload identity to service accounts
echo -e "${YELLOW}Binding workload identity...${NC}"

# Production: main branch only
gcloud iam service-accounts add-iam-policy-binding "${PROD_SA_EMAIL}" \
    --role="roles/iam.workloadIdentityUser" \
    --member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-pool/attribute.ref/refs/heads/main" \
    --project=$PROJECT_ID \
    --quiet

# Staging: develop branch
gcloud iam service-accounts add-iam-policy-binding "${STAGING_SA_EMAIL}" \
    --role="roles/iam.workloadIdentityUser" \
    --member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-pool/attribute.ref/refs/heads/develop" \
    --project=$PROJECT_ID \
    --quiet

echo -e "${GREEN}✓ Workload identity bindings created${NC}"

# Step 7: Create Artifact Registry repository
echo -e "${YELLOW}Creating Artifact Registry repository...${NC}"
if gcloud artifacts repositories describe apps \
    --location=$REGION \
    --project=$PROJECT_ID &amp;&gt;/dev/null; then
    echo -e "${GREEN}Repository already exists, skipping...${NC}"
else
    gcloud artifacts repositories create apps \
        --repository-format=docker \
        --location=$REGION \
        --description="Docker images for applications" \
        --project=$PROJECT_ID
    echo -e "${GREEN}✓ Artifact Registry created${NC}"
fi

# Step 8: Create Cloud SQL instance
echo -e "${YELLOW}Creating Cloud SQL instance (this may take 5-10 minutes)...${NC}"
if gcloud sql instances describe $DB_INSTANCE_NAME \
    --project=$PROJECT_ID &amp;&gt;/dev/null; then
    echo -e "${GREEN}Cloud SQL instance already exists, skipping...${NC}"
else
    gcloud sql instances create $DB_INSTANCE_NAME \
        --database-version=POSTGRES_15 \
        --tier=db-custom-2-7680 \
        --region=$REGION \
        --network=projects/$PROJECT_ID/global/networks/default \
        --no-assign-ip \
        --database-flags=cloudsql.iam_authentication=on,log_connections=on,log_disconnections=on \
        --backup-start-time=03:00 \
        --enable-bin-log \
        --enable-point-in-time-recovery \
        --maintenance-window-day=SUN \
        --maintenance-window-hour=4 \
        --project=$PROJECT_ID
    echo -e "${GREEN}✓ Cloud SQL instance created${NC}"
fi

# Step 9: Create database
echo -e "${YELLOW}Creating database...${NC}"
if gcloud sql databases describe $DB_NAME \
    --instance=$DB_INSTANCE_NAME \
    --project=$PROJECT_ID &amp;&gt;/dev/null; then
    echo -e "${GREEN}Database already exists, skipping...${NC}"
else
    gcloud sql databases create $DB_NAME \
        --instance=$DB_INSTANCE_NAME \
        --project=$PROJECT_ID
    echo -e "${GREEN}✓ Database created${NC}"
fi

# Step 10: Create IAM database users
echo -e "${YELLOW}Creating IAM database users...${NC}"

# Production IAM user
if gcloud sql users describe "${PROD_SA_EMAIL}" \
    --instance=$DB_INSTANCE_NAME \
    --project=$PROJECT_ID &amp;&gt;/dev/null; then
    echo -e "${GREEN}Production IAM user already exists, skipping...${NC}"
else
    gcloud sql users create "${PROD_SA_EMAIL}" \
        --instance=$DB_INSTANCE_NAME \
        --type=CLOUD_IAM_SERVICE_ACCOUNT \
        --project=$PROJECT_ID
    echo -e "${GREEN}✓ Production IAM user created${NC}"
fi

# Staging IAM user
if gcloud sql users describe "${STAGING_SA_EMAIL}" \
    --instance=$DB_INSTANCE_NAME \
    --project=$PROJECT_ID &amp;&gt;/dev/null; then
    echo -e "${GREEN}Staging IAM user already exists, skipping...${NC}"
else
    gcloud sql users create "${STAGING_SA_EMAIL}" \
        --instance=$DB_INSTANCE_NAME \
        --type=CLOUD_IAM_SERVICE_ACCOUNT \
        --project=$PROJECT_ID
    echo -e "${GREEN}✓ Staging IAM user created${NC}"
fi

# Step 11: Create secrets in Secret Manager
echo -e "${YELLOW}Creating secrets...${NC}"

# Generate a strong password (as fallback)
DB_PASSWORD=$(openssl rand -base64 32)

if gcloud secrets describe db-password \
    --project=$PROJECT_ID &amp;&gt;/dev/null; then
    echo -e "${GREEN}Secret already exists, skipping...${NC}"
else
    echo -n "$DB_PASSWORD" | gcloud secrets create db-password \
        --data-file=- \
        --replication-policy="automatic" \
        --project=$PROJECT_ID
    echo -e "${GREEN}✓ Database password secret created${NC}"
fi

# Grant access to secrets
gcloud secrets add-iam-policy-binding db-password \
    --member="serviceAccount:${PROD_SA_EMAIL}" \
    --role="roles/secretmanager.secretAccessor" \
    --project=$PROJECT_ID \
    --quiet

gcloud secrets add-iam-policy-binding db-password \
    --member="serviceAccount:${STAGING_SA_EMAIL}" \
    --role="roles/secretmanager.secretAccessor" \
    --project=$PROJECT_ID \
    --quiet

# Step 12: Enable audit logging
echo -e "${YELLOW}Configuring audit logging...${NC}"
cat &gt; /tmp/audit-config.json &lt;&lt;EOF
{
  "auditConfigs": [
    {
      "service": "iam.googleapis.com",
      "auditLogConfigs": [
        {"logType": "ADMIN_READ"},
        {"logType": "DATA_READ"},
        {"logType": "DATA_WRITE"}
      ]
    },
    {
      "service": "cloudsql.googleapis.com",
      "auditLogConfigs": [
        {"logType": "ADMIN_READ"},
        {"logType": "DATA_READ"},
        {"logType": "DATA_WRITE"}
      ]
    }
  ]
}
EOF

# Note: Full audit config merge would require getting current policy first
echo -e "${YELLOW}Note: Audit logging configuration template created at /tmp/audit-config.json${NC}"
echo -e "${YELLOW}Apply manually if needed to preserve existing policies${NC}"

# Step 13: Create monitoring alerts
echo -e "${YELLOW}Creating monitoring metrics and alerts...${NC}"

# Workload Identity failures
gcloud logging metrics create workload-identity-failures \
    --description="Failed workload identity token exchanges" \
    --log-filter='protoPayload.methodName="GenerateAccessToken" AND protoPayload.status.code!=0' \
    --project=$PROJECT_ID &amp;&gt;/dev/null || echo -e "${YELLOW}Metric already exists${NC}"

# Cloud SQL connection failures
gcloud logging metrics create cloudsql-connection-failures \
    --description="Failed Cloud SQL connections" \
    --log-filter='resource.type="cloudsql_database" AND severity="ERROR"' \
    --project=$PROJECT_ID &amp;&gt;/dev/null || echo -e "${YELLOW}Metric already exists${NC}"

echo -e "${GREEN}✓ Monitoring metrics created${NC}"

# Step 14: Output important information
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}Setup Complete!${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
echo -e "${YELLOW}Important Information:${NC}"
echo ""
echo "Workload Identity Provider:"
echo "  projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/github-pool/providers/github-provider"
echo ""
echo "Service Accounts:"
echo "  Production: $PROD_SA_EMAIL"
echo "  Staging: $STAGING_SA_EMAIL"
echo ""
echo "Cloud SQL Instance:"
echo "  Connection Name: $PROJECT_ID:$REGION:$DB_INSTANCE_NAME"
echo "  Database: $DB_NAME"
echo ""
echo "Artifact Registry:"
echo "  Repository: $REGION-docker.pkg.dev/$PROJECT_ID/apps"
echo ""
echo -e "${YELLOW}Next Steps:${NC}"
echo "1. Copy the Workload Identity Provider string above to your GitHub workflow"
echo "2. Add PROJECT_ID to your GitHub repository secrets"
echo "3. Configure database grants by connecting to Cloud SQL"
echo "4. Test the workflow with a deployment to develop branch (staging)"
echo "5. After validation, deploy to main branch (production)"
echo ""
echo -e "${GREEN}Save this output for reference!${NC}"
</code></pre><h3 id="using-the-provisioning-script">Using the Provisioning Script</h3><p>Save the script and run it:</p><pre><code class="language-bash"># Make executable
chmod +x complete-setup.sh

# Run with your configuration
./complete-setup.sh
</code></pre><h3 id="incremental-updates-with-gcloud">Incremental Updates with gcloud</h3><p>For updating specific components:</p><p><strong>Update Workload Identity Provider:</strong></p><pre><code class="language-bash"># Add new attribute mapping
gcloud iam workload-identity-pools providers update-oidc "github-provider" \
    --location="global" \
    --workload-identity-pool="github-pool" \
    --attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner,attribute.ref=assertion.ref,attribute.environment=assertion.environment" \
    --project=$PROJECT_ID

# Update attribute condition
gcloud iam workload-identity-pools providers update-oidc "github-provider" \
    --location="global" \
    --workload-identity-pool="github-pool" \
    --attribute-condition="assertion.repository_owner=='your-org' &amp;&amp; assertion.repository.startsWith('your-org/prod-')" \
    --project=$PROJECT_ID
</code></pre><p><strong>Add New Service Account Binding:</strong></p><pre><code class="language-bash"># Bind new repository
gcloud iam service-accounts add-iam-policy-binding "${PROD_SA_EMAIL}" \
    --role="roles/iam.workloadIdentityUser" \
    --member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-pool/attribute.repository/your-org/new-repo" \
    --project=$PROJECT_ID
</code></pre><p><strong>Update Cloud SQL Configuration:</strong></p><pre><code class="language-bash"># Increase instance size
gcloud sql instances patch production-db \
    --tier=db-custom-4-15360 \
    --project=$PROJECT_ID

# Add database flags
gcloud sql instances patch production-db \
    --database-flags=cloudsql.iam_authentication=on,max_connections=200,shared_buffers=2GB \
    --project=$PROJECT_ID

# Enable high availability
gcloud sql instances patch production-db \
    --availability-type=REGIONAL \
    --project=$PROJECT_ID
</code></pre><h3 id="cross-project-access">Cross-Project Access</h3><p>Enable a service account in one project to be impersonated from another:</p><pre><code class="language-bash"># Project A: Create workload identity pool
export PROJECT_A="project-a-id"
export PROJECT_B="project-b-id"
export PROJECT_A_NUMBER=$(gcloud projects describe $PROJECT_A --format="value(projectNumber)")

# Create pool in Project A
gcloud iam workload-identity-pools create "cross-project-pool" \
    --location="global" \
    --display-name="Cross-Project Pool" \
    --project=$PROJECT_A

# Create provider in Project A
gcloud iam workload-identity-pools providers create-oidc "cross-project-provider" \
    --location="global" \
    --workload-identity-pool="cross-project-pool" \
    --display-name="Cross-Project Provider" \
    --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
    --attribute-condition="assertion.repository_owner=='your-org'" \
    --issuer-uri="https://token.actions.githubusercontent.com" \
    --project=$PROJECT_A

# Create service account in Project B
gcloud iam service-accounts create cross-project-sa \
    --display-name="Cross-Project SA" \
    --project=$PROJECT_B

# Grant impersonation from Project A pool to Project B service account
gcloud iam service-accounts add-iam-policy-binding \
    "cross-project-sa@${PROJECT_B}.iam.gserviceaccount.com" \
    --role="roles/iam.workloadIdentityUser" \
    --member="principalSet://iam.googleapis.com/projects/${PROJECT_A_NUMBER}/locations/global/workloadIdentityPools/cross-project-pool/attribute.repository/your-org/your-repo" \
    --project=$PROJECT_B

# Grant Project B service account permissions in Project B
gcloud projects add-iam-policy-binding $PROJECT_B \
    --member="serviceAccount:cross-project-sa@${PROJECT_B}.iam.gserviceaccount.com" \
    --role="roles/cloudsql.client" \
    --project=$PROJECT_B
</code></pre><h3 id="cleanup-script">Cleanup Script</h3><p>To remove all resources:</p><pre><code class="language-bash">#!/bin/bash
# cleanup.sh - Remove all Workload Identity Federation resources

export PROJECT_ID="your-project-id"
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")

echo "Removing service account bindings..."
gcloud iam service-accounts remove-iam-policy-binding \
    "github-actions-prod-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role="roles/iam.workloadIdentityUser" \
    --member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-pool/attribute.ref/refs/heads/main" \
    --project=$PROJECT_ID

echo "Deleting workload identity provider..."
gcloud iam workload-identity-pools providers delete "github-provider" \
    --location="global" \
    --workload-identity-pool="github-pool" \
    --project=$PROJECT_ID \
    --quiet

echo "Deleting workload identity pool..."
gcloud iam workload-identity-pools delete "github-pool" \
    --location="global" \
    --project=$PROJECT_ID \
    --quiet

echo "Deleting service accounts..."
gcloud iam service-accounts delete "github-actions-prod-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
    --project=$PROJECT_ID \
    --quiet

gcloud iam service-accounts delete "github-actions-staging-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
    --project=$PROJECT_ID \
    --quiet

echo "Cleanup complete!"
</code></pre><h3 id="token-lifetime-and-caching">Token Lifetime and Caching</h3><p>Understand token lifetimes:</p><ul><li><strong>OIDC tokens from GitHub</strong>: Valid for 10 minutes</li><li><strong>GCP access tokens</strong>: Valid for 1 hour by default</li><li><strong>The auth action caches tokens</strong>: Reuses valid tokens within the workflow</li></ul><p>For long-running jobs, tokens are automatically refreshed.</p><h2 id="performance-optimization">Performance Optimization</h2><h3 id="1-cache-authentication-step">1. Cache Authentication Step</h3><p>The <code>google-github-actions/auth</code> action automatically caches credentials. Ensure subsequent steps reuse them:</p><pre><code class="language-yaml">steps:
  - id: 'auth'
    uses: 'google-github-actions/auth@v2'
    with:
      workload_identity_provider: '...'
      service_account: '...'
      token_format: 'access_token'  # Cache access token
      access_token_lifetime: '3600s'  # 1 hour
</code></pre><h3 id="2-parallel-job-authentication">2. Parallel Job Authentication</h3><p>Each job needs separate authentication:</p><pre><code class="language-yaml">jobs:
  deploy-frontend:
    runs-on: ubuntu-latest
    steps:
      - id: 'auth'
        uses: 'google-github-actions/auth@v2'
        # ... auth config
  
  deploy-backend:
    runs-on: ubuntu-latest
    steps:
      - id: 'auth'
        uses: 'google-github-actions/auth@v2'
        # ... same auth config
</code></pre><p>Both jobs authenticate independently in parallel.</p><h2 id="monitoring-and-observability">Monitoring and Observability</h2><h3 id="create-custom-dashboard">Create Custom Dashboard</h3><p>Monitor Workload Identity Federation usage:</p><pre><code class="language-bash"># Create log-based metrics
gcloud logging metrics create wif-token-exchanges \
    --description="Workload Identity token exchanges" \
    --log-filter='protoPayload.methodName="GenerateAccessToken"'

gcloud logging metrics create wif-successful-exchanges \
    --description="Successful WIF exchanges" \
    --log-filter='protoPayload.methodName="GenerateAccessToken" AND protoPayload.status.code=0'

gcloud logging metrics create wif-failed-exchanges \
    --description="Failed WIF exchanges" \
    --log-filter='protoPayload.methodName="GenerateAccessToken" AND protoPayload.status.code!=0'
</code></pre><h3 id="query-recent-authentications">Query Recent Authentications</h3><pre><code class="language-bash"># Last 10 authentication attempts
gcloud logging read \
    'protoPayload.methodName="GenerateAccessToken"
    AND resource.labels.service_account_id:"github-actions-sa"' \
    --limit=10 \
    --format='table(timestamp,protoPayload.authenticationInfo.principalEmail,protoPayload.status.code)' \
    --project=$PROJECT_ID
</code></pre><h2 id="cost-considerations">Cost Considerations</h2><p>Workload Identity Federation has <strong>no additional cost</strong>:</p><ul><li>✓ Token exchange operations: Free</li><li>✓ Workload Identity Pools: Free</li><li>✓ Workload Identity Providers: Free</li><li>✓ IAM operations: Free (within quota)</li></ul><p>You only pay for:</p><ul><li>Resources accessed by the service account (Cloud Run, BigQuery, etc.)</li><li>Cloud Audit Logs storage (if enabled for Data Access logs)</li></ul><h2 id="migration-from-service-account-keys">Migration from Service Account Keys</h2><p>If you're currently using service account keys, here's the migration path:</p><h3 id="step-1-set-up-workload-identity-federation-as-shown-above">Step 1: Set up Workload Identity Federation (as shown above)</h3><h3 id="step-2-update-workflows-to-use-both-methods-temporarily">Step 2: Update workflows to use both methods temporarily</h3><pre><code class="language-yaml">- id: 'auth'
  uses: 'google-github-actions/auth@v2'
  with:
    workload_identity_provider: 'projects/.../providers/github-provider'
    service_account: 'github-actions-sa@project.iam.gserviceaccount.com'
    # Fallback to key if WIF fails during migration
    credentials_json: ${{ secrets.GCP_SA_KEY }}
</code></pre><h3 id="step-3-test-thoroughly-in-staging">Step 3: Test thoroughly in staging</h3><h3 id="step-4-remove-the-fallback">Step 4: Remove the fallback</h3><pre><code class="language-yaml">- id: 'auth'
  uses: 'google-github-actions/auth@v2'
  with:
    workload_identity_provider: 'projects/.../providers/github-provider'
    service_account: 'github-actions-sa@project.iam.gserviceaccount.com'
    # No fallback - fully migrated to WIF
</code></pre><h3 id="step-5-delete-service-account-keys">Step 5: Delete service account keys</h3><pre><code class="language-bash"># List keys
gcloud iam service-accounts keys list \
    --iam-account=$SA_EMAIL \
    --project=$PROJECT_ID

# Delete each key
gcloud iam service-accounts keys delete KEY_ID \
    --iam-account=$SA_EMAIL \
    --project=$PROJECT_ID
</code></pre><h3 id="step-6-remove-github-secrets">Step 6: Remove GitHub Secrets</h3><p>Delete the <code>GCP_SA_KEY</code> secret from your repository settings.</p><h2 id="conclusion">Conclusion</h2><p>Workload Identity Federation represents a significant security improvement over traditional service account keys. By implementing the steps in this guide, you've:</p><p>✓ Eliminated long-lived credentials from your CI/CD pipeline<br>✓ Reduced the risk of credential leakage<br>✓ Simplified credential rotation (it's now automatic)<br>✓ Improved audit trail with detailed token exchange logs<br>✓ Implemented fine-grained access control based on repository, branch, and actor<br>✓ Secured database access with Cloud SQL IAM authentication<br>✓ Automated database migrations without storing database credentials</p>]]></content:encoded>
        </item>
        <item>
            <title>How to change the default login redirect in Laravel 12 (The Simple Way)</title>
            <link>https://sysadmin-journal.com/how-to-change-the-default-login-redirect-in-laravel-12-the-simple-way</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/how-to-change-the-default-login-redirect-in-laravel-12-the-simple-way</guid>
            <pubDate>Tue, 06 Jan 2026 14:21:48 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>Laravel</category>
            <description>Stop digging through middleware! Discover the simplest way to change the default /dashboard login redirect in Laravel 12.x by tweaking a single line in your config/fortify.php file.</description>
            <content:encoded><![CDATA[<p>If you have recently spun up a new Laravel 12.x application using a starter kit (Livewire, React, etc), you are likely familiar with this flow: you log in successfully, and the application immediately redirects you to <code>/dashboard</code>.</p><p>While <code>/dashboard</code> is a sensible default for many SaaS applications, it isn't always what you need. Perhaps you are building an admin panel that lives at <code>/panel</code>, or maybe you are building a membership site or e-commerce store where the user should simply return to the homepage (<code>/</code>) after logging in.</p><p>If you dive into the official documentation, you might find yourself deep in <code>bootstrap/app.php</code> trying to customize the <code>auth</code> middleware logic. While that works, there is a much simpler configuration change that handles this in seconds.</p><h3 id="the-hard-way-middleware">The "Hard" Way: Middleware</h3><p>Typically, the documentation guides developers to intercept the request within the <code>bootstrap/app.php</code> file to handle redirection for guests and authenticated users. This involves defining closures and logic that can clutter your bootstrap file if you only need a simple path change.</p><h3 id="the-easy-way-config-configuration">The "Easy" Way: Config Configuration</h3><p>If your starter kit is powered by <a href="https://laravel.com/docs/12.x/fortify" rel="noreferrer"><strong>Laravel Fortify</strong></a> (which handles the backend authentication logic for kits like Jetstream), you don't need to touch your middleware or controllers. You can control this behavior directly from your configuration files.</p><p>Here is the one-line fix:</p><ol><li>Navigate to <strong><code>config/fortify.php</code></strong>.</li><li>Locate the <code>'home'</code> key (usually near the top of the file).</li><li>Change the value from <code>RouteServiceProvider::HOME</code> (or <code>'/dashboard'</code>) to your desired path.</li></ol><p>To redirect to the Homepage:</p><pre><code>'home' =&gt; '/',</code></pre><p>To redirect to a custom Admin Panel:</p><pre><code>'home' =&gt; '/panel',</code></pre><h3 id="why-this-works">Why this works</h3><p>Laravel Fortify uses this configuration value to determine where to send the user immediately after the authentication guard confirms their credentials. By changing it here, you ensure that the redirect is consistent across your application without having to write custom redirection logic in your controllers.</p><p>Don't overcomplicate your authentication flow. Before you start writing custom middleware logic in Laravel 12, check your <code>config/fortify.php</code> file. A simple string change is often all it takes to get your users exactly where they need to go.</p>]]></content:encoded>
        </item>
        <item>
            <title>Angular Material 3: How to Generate and Apply a Custom Color Palette</title>
            <link>https://sysadmin-journal.com/angular-material-3-how-to-generate-and-apply-a-custom-color-palette</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/angular-material-3-how-to-generate-and-apply-a-custom-color-palette</guid>
            <pubDate>Tue, 30 Dec 2025 09:30:22 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>Angular</category>
            <description>Pre-built Angular Material 3 palettes not fitting your brand? Learn how to generate a custom color palette and correctly apply it to your Angular project.</description>
            <content:encoded><![CDATA[<p>For a new project at work, I recently decided to explore different front-end frameworks. I settled on <strong>Angular</strong>, using a starter kit that included <strong>Tailwind CSS</strong>. To speed up development, I also added <strong>Angular Material 3</strong> to benefit from its pre-built components, things like toggle buttons and side menus are just easier when you don't have to build them from scratch.</p><p>Since I am quite comfortable with Tailwind CSS, I assumed customization would be a breeze. However, I am new to the Angular ecosystem and Material 3, and I face issues almost immediately regarding theming.</p><h3 id="the-problem-pre-built-palettes-are-limited">The Problem: Pre-built Palettes Are Limited</h3><p>At setup time, I selected the standard Azure color palette. It looked decent enough for the toggle buttons initially, but as the UI evolved, I needed to change the color to match the specific primary color we were using for the rest of the project.</p><p>This turned out to be less straightforward than I expected.</p><p>I learned that Angular Material comes with several pre-built palettes, such as:</p><ul><li>red-palette</li><li>blue-palette</li><li>azure-palette</li><li>violet-palette</li><li>...and about eight others.</li></ul><p>While these cover the basics, none of them matched my specific branding needs. I needed a custom palette, not a generic preset.</p><h3 id="the-solution-the-theme-generator">The Solution: The Theme Generator</h3><p>Digging deeper into the official documentation, I discovered that Angular provides a CLI tool to generate a custom palette based on your specific hex codes.</p><p>You can run the following command:</p><pre><code>ng generate @angular/material:theme-color</code></pre><p>This interactive tool asks you to define your primary, secondary, and neutral colors. Once finished, it produces a CSS (or SCSS) file with your generated theme variables.</p><h3 id="the-missing-piece-how-to-use-it">The Missing Piece: How to Use It?</h3><p>Here is where I got stuck. The tool generates the file—let's call it <code>custom.css</code>—but the documentation wasn't immediately clear on <em>how</em> to actually apply this new file to the project, specifically regarding where it fits into the existing <code>material-theme.scss</code>.</p><p>After some trial and error, I found the solution. You need to explicitly import your new custom styles into your main theme file.</p><p><strong>1. Import the custom file</strong> Open your <code>material-theme.scss</code> file and add the use rule pointing to your generated file:</p><pre><code>@use './app/styles/custom.css';</code></pre><p><strong>2. Disable the pre-built theme</strong> This is the crucial step. If you simply import your custom CSS, it might conflict with or be overridden by the default configuration you set up during installation.</p><p>You must comment out or remove the existing <code>@include mat.theme</code> block that references the old palette (like <code>$azure-palette</code>).</p><p>It should look something like this:</p><pre><code>// html {
//   height: 100%;
//   @include mat.theme((
//     color: (
//       primary: mat.$azure-palette,
//       tertiary: mat.$azure-palette,
//     ),
//     typography: Roboto,
//     density: 0,
//   ));
// }</code></pre><p>By removing the default theme inclusion and importing your generated <code>custom.css</code>, Angular Material will now respect the specific color definitions you generated via the CLI.</p><h2 id="note">Note</h2><p>You might notice that the hex color provided and the one set as primary color in the generated theme file are different. The tone changes slightly. If that's something which bothers you and you need the exact hex color as your primary color in the theme, then you can update the "primary palette" from the generated CSS file accordingly.</p><pre><code>  /* Primary palette variables */
  --mat-sys-primary: light-dark(#5D688A, #bac3ff);
  --mat-sys-on-primary: light-dark(#ffffff, #1e2b68);
  --mat-sys-primary-container: light-dark(#dee1ff, #364280);
  --mat-sys-on-primary-container: light-dark(#051352, #dee1ff);
  --mat-sys-inverse-primary: light-dark(#bac3ff, #4e5a99);
  --mat-sys-primary-fixed: light-dark(#dee1ff, #dee1ff);
  --mat-sys-primary-fixed-dim: light-dark(#bac3ff, #bac3ff);
  --mat-sys-on-primary-fixed: light-dark(#051352, #051352);
  --mat-sys-on-primary-fixed-variant: light-dark(#364280, #364280);</code></pre>]]></content:encoded>
        </item>
        <item>
            <title>Re-skilling for the Future: My Experience at the PMI Mauritius Conference 2025</title>
            <link>https://sysadmin-journal.com/re-skilling-for-the-future-my-experience-at-the-pmi-mauritius-conference-2025</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/re-skilling-for-the-future-my-experience-at-the-pmi-mauritius-conference-2025</guid>
            <pubDate>Wed, 22 Oct 2025 12:45:14 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>PMI Chapter of Mauritius</category>
            <description>The Project Management Institute (PMI) Chapter of Mauritius is a community of professionals dedicated to advancing the practice, science, and profession of project management in Mauritius.</description>
            <content:encoded><![CDATA[<p>I had the privilege of joining a panel discussion organized by the <strong>Project Management Institute (PMI) Chapter of Mauritius</strong> as part of their <a href="https://pmimauritius.com/event/pmi-chapter-mauritius-annual-conference-2025" rel="noreferrer">annual conference</a>. The theme this year was <em>“Re-skilling for the Future.”</em></p><p>The event took place on 8th October 2025 at the beautiful <a href="https://www.maritimresortandspa.mu/" rel="noreferrer">Maritim Resort</a>.</p>
<!--kg-card-begin: html-->
<div class="grid place-items-center">
  <iframe src="https://www.linkedin.com/embed/feed/update/urn:li:share:7378329948530479104?collapsed=1" height="670" width="504" frameborder="0" allowfullscreen="" title="Embedded post"></iframe>
</div>
<!--kg-card-end: html-->
<p>I was honored to share the stage with some great panelists:</p><ul><li>Prof. (Dr.) Kiran Bhujun, Director of Tertiary Education &amp; Scientific Research</li><li>Shalini Bunwaree Nagdan, Founder of Unlock Synergies, Strategy Consultant, Corporate Trainer</li><li>Yogesh Jankee, Vice President – Services, Dayforce</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2025/10/pmi-conference-2025-panel-discussion-2.jpeg" class="kg-image" alt="Sareeta Nundloll Goundan (left), Shalini Bunwaree Nagdan (second left), Ish Sookun (center), Prof. (Dr.) Kiran Bhujun (second right), Yogesh Jankee (right)" loading="lazy" width="1200" height="800" srcset="/content/images/size/w600/2025/10/pmi-conference-2025-panel-discussion-2.jpeg 600w, /content/images/size/w1000/2025/10/pmi-conference-2025-panel-discussion-2.jpeg 1000w, /content/images/2025/10/pmi-conference-2025-panel-discussion-2.jpeg 1200w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Sareeta Nundloll Goundan (left), Shalini Bunwaree Nagdan (second left), Ish Sookun (center), Prof. (Dr.) Kiran Bhujun (second right), Yogesh Jankee (right)</span></figcaption></figure><p>The discussion was moderated by Sareeta Nundloll Goundan, President of the PMI Mauritius Chapter and Managing Director of SSL Consulting Services. Sareeta also serves as a Board Director at ABC Banking.</p><p>During the panel, I was asked about whether Artificial Intelligence (AI) is replacing people and the fear of losing jobs. I explained that there are two ways to look at it. On one hand, AI can write code, so it’s easy to assume we no longer need developers but that’s not entirely true. AI isn’t replacing developers; it’s enhancing their productivity. Yes, a team that once needed ten developers for twenty projects might now manage with five, which can make it seem like AI is taking jobs. However, that same team of ten could now deliver five or more additional projects thanks to increased efficiency. My main point was that people need to learn <em>how to learn</em>, and <em>learn fast</em> in order to stay relevant in this evolving landscape. As Sareeta added, it’s equally important to be able to <em>unlearn and relearn</em> when the world changes.</p><h2 id="a-first-pmi-conference-experience">A first PMI Conference Experience</h2><p>I joined the PMI Chapter earlier this year and have attended a few of their <a href="https://sysadmin-journal.com/tag/meetup/" rel="noreferrer">meetups</a>, but this was my first experience participating in the PMI Mauritius Conference. I’m glad I accepted the invitation as it was both insightful and energizing.</p><p>The conference gathered professionals from a variety of industries, creating a unique opportunity to exchange ideas beyond the traditional IT sphere. Engaging with people who approach challenges through different lenses was a refreshing experience indeed.</p><p>One of the attendees, Ashwini G. Surnam, Senior Manager – Talent Acquisition at Dayforce, shared her <a href="https://www.linkedin.com/posts/ashwinigs_dayforce-pmiconference2025-activity-7381909944788467713-xK63?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAAAmXQJ8B71_0MsAm7nTMnKM0dVsdP9DsO34" rel="noreferrer">reflections on LinkedIn</a> and highlighted key lessons from the panel discussion.</p>
<!--kg-card-begin: html-->
<div class="grid place-items-center">
  <iframe src="https://www.linkedin.com/embed/feed/update/urn:li:share:7381909942959534080" height="1306" width="504" frameborder="0" allowfullscreen="" title="Embedded post"></iframe>
</div>
<!--kg-card-end: html-->
<p>I am grateful to Sareeta for the opportunity to contribute to a meaningful dialogue and to my colleague and mentor, Eddy Lareine, for inspiring me to step out of the tech bubble and explore the world of Project Management.</p><p>At the close of the event, I had an engaging conversation with Billy S. Mwape, CIO of FNB Zambia and a mentor for the PMI Sub-Saharan Africa region.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2025/10/Meeting-Billy-S-Mwape-at-PMI-Conference-Mauritius.jpeg" class="kg-image" alt="Billy S. Mwape (left), Eddy Lareine (center), Ish Sookun (right)" loading="lazy" width="2000" height="1500" srcset="/content/images/size/w600/2025/10/Meeting-Billy-S-Mwape-at-PMI-Conference-Mauritius.jpeg 600w, /content/images/size/w1000/2025/10/Meeting-Billy-S-Mwape-at-PMI-Conference-Mauritius.jpeg 1000w, /content/images/size/w1600/2025/10/Meeting-Billy-S-Mwape-at-PMI-Conference-Mauritius.jpeg 1600w, /content/images/size/w2400/2025/10/Meeting-Billy-S-Mwape-at-PMI-Conference-Mauritius.jpeg 2400w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Billy S. Mwape (left), Eddy Lareine (center), Ish Sookun (right)</span></figcaption></figure><p>We spoke about <em>Africans building for Africa in Africa</em>, a topic that resonates with both of us. I shared my own journey and the challenges I’ve faced setting up Linux mirrors across the African continent, an effort driven by the belief that local infrastructure empowers local innovation.&nbsp;Naturally, the conversation took a technical turn at times. Billy passionately advocates for putting Africans on the global stage, a vision I wholeheartedly share. We also reflected on our experiences at international conferences, and the rich contrasts we’ve observed while traveling abroad and exploring different cultures.</p><p>Putting together a conference like this, which is attended by hundreds of professionals is not an easy feat. I can imagine the countless volunteer hours that must have been put to make it happen. Therefore, kudos to the organising team and all volunteers involved, and the sponsors for backing such an event financially.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2025/10/pmi-conference-2025-group-picture.jpeg" class="kg-image" alt="PMI Conference 2025 — Mauritius, Group Photo" loading="lazy" width="1200" height="800" srcset="/content/images/size/w600/2025/10/pmi-conference-2025-group-picture.jpeg 600w, /content/images/size/w1000/2025/10/pmi-conference-2025-group-picture.jpeg 1000w, /content/images/2025/10/pmi-conference-2025-group-picture.jpeg 1200w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">PMI Conference 2025 — Mauritius, Group Photo</span></figcaption></figure>]]></content:encoded>
        </item>
        <item>
            <title>Programmers Day 2025</title>
            <link>https://sysadmin-journal.com/programmers-day-2025</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/programmers-day-2025</guid>
            <pubDate>Thu, 02 Oct 2025 07:06:44 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>Meetup</category>
            <description>Programmers Day, also known as the Day of the Programmer and occasionally as Day 2^8, is an international professional day that is celebrated on the 256th day of each year. This is September 13 in common years and September 12 in leap years. (Source: Wikipedia)</description>
            <content:encoded><![CDATA[<p>About two months ago, Mervyn reached out to me saying that he had an event in mind, which he would like to discuss. He wanted to plan and organize a <strong>Programmers Day</strong> which would bring together all user groups of Mauritius under one roof and that each user group could showcase their activities. It often happens that we get query from people about what user groups are active in Mauritius. So, having this event that focused solely on the user group activities rather than the technologies they advocate for, sounded like a nice idea.</p><p>First things first, we needed a venue for the event and time was running short. I contacted the African Leadership College of Higher Education to see if they could provide the venue and they gladly accepted. Next was to find a date for the event. The <a href="https://www.timeanddate.com/holidays/world/international-programmers-day" rel="noreferrer"><strong>International Programmers Day</strong></a> is usually celebrated on the 12th or 13th September (depending on leap years). This year, it was celebrated on the 13th of September. However, that also meant it was the second Saturday of the month, when the Mauritius Software Craftsmanship Community holds its <a href="https://www.meetup.com/mauritiussoftwarecraftsmanshipcommunity/events/305532107/" rel="noreferrer">monthly meetup</a>. Then, a week later, i.e on the 20th of September, the <a href="https://coders.mu/meetup/65" rel="noreferrer">Front-end Coders</a> held their monthly meetup. Therefore, to avoid a clash of events on those dates, Mervyn agreed that we hold the Programmers Day on the last Saturday of September, i.e the 27th.</p><p>Mervyn created a WhatsApp group with people from the different user groups wishing to help. Soon after, many started pushing ideas, like Alex working on the event poster, Shelly drafting the agenda and Mervyn looking for a catering sponsor.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2025/10/programmers-day-whatsapp-1.jpg" class="kg-image" alt="Activity on the WhatsApp Group" loading="lazy" width="1976" height="1580" srcset="/content/images/size/w600/2025/10/programmers-day-whatsapp-1.jpg 600w, /content/images/size/w1000/2025/10/programmers-day-whatsapp-1.jpg 1000w, /content/images/size/w1600/2025/10/programmers-day-whatsapp-1.jpg 1600w, /content/images/2025/10/programmers-day-whatsapp-1.jpg 1976w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Activity on the WhatsApp Group</span></figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2025/10/prgrammers-day-2025-agenda.jpeg" class="kg-image" alt="Programmers' Day agenda by Shelly" loading="lazy" width="905" height="1280" srcset="/content/images/size/w600/2025/10/prgrammers-day-2025-agenda.jpeg 600w, /content/images/2025/10/prgrammers-day-2025-agenda.jpeg 905w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Programmers' Day agenda by Shelly</span></figcaption></figure><p>Finally, on the 27th of September, all user groups (except for the AWS User Group), came together at the African Leadership College of Higher Education, and they presented their user group activities, things they organised and achieved in the past, e.g participating in global events, and also sharing their plans for the future.</p><p>Alex Bissessur presented the <a href="https://cloudnativemauritius.com" rel="noreferrer">Cloud Native Community Group – Mauritius</a>, which is the local chapter of the Cloud Native Computing Foundation. He spoke about the group's meetups, activities, and projects that some of the community members are working on, and mentioned the group's upcoming event — Hacktoberfest!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2025/10/hacktoberfest-2025.jpeg" class="kg-image" alt="Hacktoberfest by Cloud Native Mauritius" loading="lazy" width="1280" height="1280" srcset="/content/images/size/w600/2025/10/hacktoberfest-2025.jpeg 600w, /content/images/size/w1000/2025/10/hacktoberfest-2025.jpeg 1000w, /content/images/2025/10/hacktoberfest-2025.jpeg 1280w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Hacktoberfest by Cloud Native Community Group Mauritius</span></figcaption></figure><p>For more details about the CNCG Mauritius – Hacktoberfest, ping <a href="https://x.com/Alex_with_a_B" rel="noreferrer">Alex</a> or join the <a href="https://t.me/+E_G1XIVXTWMzYjU0" rel="noreferrer">CNCG Mauritius Telegram</a> group.</p><p>I spoke at 2 p.m. on the <a href="https://www.opensuse.org" rel="noreferrer">openSUSE Project</a>. I briefly covered the history of S.u.S.E since 1992 and the creation of openSUSE in 2005. I then explained that the openSUSE Project isn't a <em>Linux distribution</em> but a project that houses several distributions, like Leap, Leap Micro, Tumbleweed, MicroOS, Aeon and Kalpa, and also several other applications, like Open Build Service and OpenQA, just to name a few.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2025/10/openSUSE-Mauritius-at-ALCHE-2.jpeg" class="kg-image" alt="Ish Sookun (myself) speaking while Alex providing tech support in the background" loading="lazy" width="1600" height="1200" srcset="/content/images/size/w600/2025/10/openSUSE-Mauritius-at-ALCHE-2.jpeg 600w, /content/images/size/w1000/2025/10/openSUSE-Mauritius-at-ALCHE-2.jpeg 1000w, /content/images/2025/10/openSUSE-Mauritius-at-ALCHE-2.jpeg 1600w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Ish Sookun (myself) speaking while Alex providing tech support in the background</span></figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2025/10/openSUSE-Mauritius-at-ALCHE-1.jpeg" class="kg-image" alt="Photos by Chittesh Sham" loading="lazy" width="1600" height="1200" srcset="/content/images/size/w600/2025/10/openSUSE-Mauritius-at-ALCHE-1.jpeg 600w, /content/images/size/w1000/2025/10/openSUSE-Mauritius-at-ALCHE-1.jpeg 1000w, /content/images/2025/10/openSUSE-Mauritius-at-ALCHE-1.jpeg 1600w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Photos by Chittesh Sham</span></figcaption></figure><p>I spoke about the myriad ways that one can contribute to the project, with a special mention about the <a href="https://mirrors.opensuse.org" rel="noreferrer">openSUSE mirrors</a> that we've built in Mauritius.</p><p>openSUSE Mauritius is not a user group in the traditional sense. I explained that it is an umbrella of contributions to the upstream project by people from Mauritius.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2025/10/JoKi_at_Programmers_Day_2025.jpeg" class="kg-image" alt="Jochen Kirstätter (JoKi) presenting MSCC and GDG Mauritius" loading="lazy" width="1280" height="960" srcset="/content/images/size/w600/2025/10/JoKi_at_Programmers_Day_2025.jpeg 600w, /content/images/size/w1000/2025/10/JoKi_at_Programmers_Day_2025.jpeg 1000w, /content/images/2025/10/JoKi_at_Programmers_Day_2025.jpeg 1280w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Jochen Kirstätter (JoKi) presenting MSCC and GDG Mauritius</span></figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2025/10/Chittesh_Sham_at_Programmers_Day_2025.jpeg" class="kg-image" alt="Chittesh Sham and Aditya Bholah presenting PyData Mauritius" loading="lazy" width="1280" height="960" srcset="/content/images/size/w600/2025/10/Chittesh_Sham_at_Programmers_Day_2025.jpeg 600w, /content/images/size/w1000/2025/10/Chittesh_Sham_at_Programmers_Day_2025.jpeg 1000w, /content/images/2025/10/Chittesh_Sham_at_Programmers_Day_2025.jpeg 1280w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Chittesh Sham and Aditya Bholah presenting PyData Mauritius</span></figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2025/10/Frontend_Coders_at_Programmers_day_2025.jpeg" class="kg-image" alt="Sandeep, Cedric and Kushul presenting Front-end Coders" loading="lazy" width="1280" height="960" srcset="/content/images/size/w600/2025/10/Frontend_Coders_at_Programmers_day_2025.jpeg 600w, /content/images/size/w1000/2025/10/Frontend_Coders_at_Programmers_day_2025.jpeg 1000w, /content/images/2025/10/Frontend_Coders_at_Programmers_day_2025.jpeg 1280w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Sandeep, Cedric and Kushul presenting Front-end Coders</span></figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2025/10/NUGM_at_Programmers_Day_2025.jpeg" class="kg-image" alt="Vidush and Fawwaz presenting the .Net User Group of Mauritius (NUGM)" loading="lazy" width="1280" height="720" srcset="/content/images/size/w600/2025/10/NUGM_at_Programmers_Day_2025.jpeg 600w, /content/images/size/w1000/2025/10/NUGM_at_Programmers_Day_2025.jpeg 1000w, /content/images/2025/10/NUGM_at_Programmers_Day_2025.jpeg 1280w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Vidush and Fawwaz presenting the .Net User Group of Mauritius (NUGM)</span></figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2025/10/PyMUG_at_Programmers_Day_2025.jpeg" class="kg-image" alt="Dominique Theodore, VP of PYMUG" loading="lazy" width="960" height="1280" srcset="/content/images/size/w600/2025/10/PyMUG_at_Programmers_Day_2025.jpeg 600w, /content/images/2025/10/PyMUG_at_Programmers_Day_2025.jpeg 960w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Dominique Theodore, VP of PYMUG</span></figcaption></figure><p>The event ended around 3 p.m.</p>]]></content:encoded>
        </item>
        <item>
            <title>Happy 10th Anniversary, Google Kubernetes Engine! 🎂</title>
            <link>https://sysadmin-journal.com/happy-10th-anniversary-google-kubernetes-engine</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/happy-10th-anniversary-google-kubernetes-engine</guid>
            <pubDate>Fri, 22 Aug 2025 15:02:46 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>Kubernetes</category>
            <description>Kubernetes is an open-source system for deploying, scaling, and managing containerized applications. It was originally developed by Google and released in 2014 to help simplify the process of running applications at a large scale.</description>
            <content:encoded><![CDATA[<p>This year marks a major milestone – <strong>10 years</strong> since Google first pioneered Kubernetes and GKE. A decade is a lifetime in technology, and in that time, GKE has helped shape how we build and deploy applications, adapting to the rise of microservices, DevOps, cloud-native AI, and edge computing. It's no surprise that Google is the<strong> </strong><a href="https://k8s.devstats.cncf.io/d/9/companies-table?orgId=1" rel="noreferrer"><strong>top contributor</strong></a> to Kubernetes, with over one million contributions.</p><h2 id="the-kubernetes-story">The Kubernetes Story</h2><p>The story of GKE begins with<strong> Kubernetes</strong> (also known as K8s), an open-source system for managing containerized applications. Google originally <a href="https://cloud.google.com/learn/what-is-kubernetes" rel="noreferrer">developed</a> and open-sourced Kubernetes in 2014, and it was later <a href="https://www.cncf.io/projects/kubernetes/" rel="noreferrer">accepted by the Cloud Native Computing Foundation (CNCF)</a> in 2016. Google's expertise in this area is rooted in its own experience, as the company has been running its services like Gmail, YouTube, and Search on containers since the early 2000s. With billions of containers launched every week, Google has been its own best customer, and it's this decade of expertise that it shares through GKE.</p><h2 id="a-decade-of-innovation">A Decade of Innovation</h2><p>After 10 months of alpha/beta development, Google Kubernetes Engine (GKE), then known as Google Container Engine, <a href="https://cloudplatform.googleblog.com/2015/08/Google-Container-Engine-is-Generally-Available.html" rel="noreferrer">was launched on August 26, 2015</a>, as the world's first fully managed Kubernetes service. Its early success was demonstrated by customers like Niantic, whose game <a href="https://cloud.google.com/blog/products/containers-kubernetes/bringing-pokemon-go-to-life-on-google-cloud" rel="noreferrer">Pokémon GO came to life on GKE</a>.</p><p>GKE's evolution over the years has been driven by new features and increased capabilities:</p><ul><li><strong>2017</strong>: GKE's node support was increased to 5,000, and the service was officially renamed from Google Container Engine to Google Kubernetes Engine.</li><li><strong>2018</strong>: GKE was made available across both Google Cloud and on-premise environments, offering a consistent platform for hybrid cloud deployments.</li><li><strong>2019</strong>: Google announced <strong>Anthos</strong>, which went multi-cloud, allowing customers to deploy and manage applications seamlessly across on-prem, public cloud, and multiple public clouds.</li><li><strong>2020</strong>: GKE's node support was scaled up to 15,000 nodes, enabling customers like Bayer Crop Science to handle massive workloads.</li><li><strong>2021</strong>: The launch of <strong>GKE Autopilot</strong> allowed developers to focus on their workloads by having Google manage the underlying infrastructure. Other features included GPU time-sharing and a blue-green upgrade mechanism for node pools.</li><li><strong>2022</strong>: GKE <a href="https://cloud.google.com/blog/products/compute/tau-t2a-is-first-compute-engine-vm-on-an-arm-chip" rel="noreferrer">introduced support for Arm workloads</a> with Tau T2A VMs, giving customers more cost-effective options.</li></ul><p>Companies like <a href="https://cloud.google.com/customers/signify-data" rel="noreferrer">Signify</a>, formerly Philips Lighting, have used GKE as their foundation for a decade. They've scaled their infrastructure to support a fleet of over 153 million connected light points, absorbing an explosion in traffic while guaranteeing global reliability and performance. Today, Signify is leveraging GKE for new workloads like platform engineering and AI, exploring how GKE can help them transform technical signals into actionable diagnostics.</p><p>As GKE looks toward its next decade, it remains focused on helping businesses innovate faster, stay agile, and handle increasingly complex workloads, especially with the rise of AI-powered application development.</p>]]></content:encoded>
        </item>
        <item>
            <title>How to disable Python interpreter warnings in Ansible?</title>
            <link>https://sysadmin-journal.com/how-to-disable-python-interpreter-warnings-in-ansible</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/how-to-disable-python-interpreter-warnings-in-ansible</guid>
            <pubDate>Wed, 20 Aug 2025 18:23:03 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>Automation</category>
            <description>If you&#039;ve spent any time working with Ansible, you&#039;ve likely seen the infamous &quot;discovered Python interpreter&quot; warning. While it may seem harmless, understanding its cause and how to properly address it can help you run a less chatty Ansible.</description>
            <content:encoded><![CDATA[<p>When you're managing systems with Ansible, you're not just running commands on your local machine; you're orchestrating actions on remote hosts. For Ansible to execute its modules on these remote systems, it needs a way to communicate and perform tasks. This is where a Python interpreter comes in. Ansible requires a Python interpreter on the managed host to run its various modules and collect facts about the system.</p><p>By default, Ansible's <strong>"interpreter discovery"</strong> is set to <code>auto</code>. In this mode, Ansible automatically attempts to find a suitable Python interpreter on the remote host by checking common default locations, such as <code>/usr/bin/python3</code>, <code>/usr/bin/python</code>, and <code>python</code>. This feature simplifies initial setups, as you don't have to manually configure the Python path for every new host.</p><p>However, this convenience comes with a specific warning you might encounter during execution:</p><pre><code>$ ansible node-1 -i inventory -m ping
[WARNING]: Platform linux on host node-1 is using the discovered Python interpreter at /usr/bin/python3.12, but future installation of another Python interpreter could change the
meaning of that path. See https://docs.ansible.com/ansible-core/2.18/reference_appendices/interpreter_discovery.html for more information.
node-1 | SUCCESS =&gt; {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3.12"
    },
    "changed": false,
    "ping": "pong"
}</code></pre><p>This warning is essentially a proactive message from Ansible. It's telling you that while it successfully found a Python interpreter at one of its default locations, there's a possibility that a future system update or new software installation could change which interpreter is linked to that path. This could lead to unexpected behavior or module failures in later runs.</p><h3 id="resolving-the-warning">Resolving the Warning</h3><p>To eliminate this warning and ensure consistency, you have a few options. The most reliable method is to <strong>explicitly define the path to the Python interpreter</strong> you want Ansible to use. If the interpreter is installed at a specific, stable location (for example, <code>/usr/bin/python3.12</code>), you can set this path for a specific host or an entire group of hosts in your inventory file.</p><p>For example, in an INI-style inventory file:</p><pre><code>[web_servers]
node-1 ansible_python_interpreter=/usr/bin/python3.12
node-2 ansible_python_interpreter=/usr/bin/python3.12

[database_servers]
node-3
node-4

[database_servers:vars]
ansible_python_interpreter=/usr/bin/python3.9</code></pre><p>Alternatively, in a YAML-style inventory:</p><pre><code>all:
  children:
    web_servers:
      hosts:
        node-1:
          ansible_python_interpreter: /usr/bin/python3.12
        node-2:
          ansible_python_interpreter: /usr/bin/python3.12
    database_servers:
      hosts:
        node-3:
        node-4:
      vars:
        ansible_python_interpreter: /usr/bin/python3.9</code></pre><p>By doing this, you're locking in the exact interpreter path, and Ansible will no longer need to discover it, thus bypassing the warning.</p><p>If you don't want to specify the interpreter for each host, or you are certain that interpreter changes won't be an issue in your environment, you can configure Ansible to run in <strong>"auto_silent" </strong>mode. This mode instructs Ansible to still perform the automatic discovery but to suppress the warning message.</p><p>This configuration can be applied at different levels of precedence.</p><p>For global or user-specific configurations, you would add the following to your <code>ansible.cfg</code> file (either at <code>/etc/ansible/ansible.cfg</code> or <code>~/.ansible.cfg</code>):</p><pre><code>[defaults]
interpreter_python = auto_silent</code></pre><p>For individual hosts or entire groups within your inventory file, you would use the <code>ansible_python_interpreter</code> variable like so:</p><pre><code>[my_group]
host1 ansible_python_interpreter=auto_silent
host2 ansible_python_interpreter=auto_silent

[another_group:vars]
ansible_python_interpreter=auto_silent</code></pre><p>By following these steps, you can ensure your Ansible runs are clean of the Python interpreter warning, leading to a smoother and more predictable automation workflow.</p>]]></content:encoded>
        </item>
        <item>
            <title>Developers Conference 2025 – Panel Discussion</title>
            <link>https://sysadmin-journal.com/developers-conference-2025-panel-discussion</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/developers-conference-2025-panel-discussion</guid>
            <pubDate>Mon, 11 Aug 2025 04:28:02 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>MSCC</category>
            <category>Conference</category>
            <description>The Developers Conference is the largest community-driven technology event in Mauritius, bringing together software engineers, IT professionals, students, and tech enthusiasts for three days of talks, workshops, and panel discussions.</description>
            <content:encoded><![CDATA[<p>Covering topics such as software development, AI &amp; machine learning, cloud computing, DevOps, cybersecurity, and emerging technologies, the Developers Conference offers a unique platform for learning, networking, and sharing ideas within the island’s vibrant tech ecosystem.</p><p>Among the many sessions and daily keynotes, I had the opportunity to host and moderate a panel discussion titled <em>“How Much Cybersecurity is Enough?”,</em> a timely topic given the rapidly evolving threat landscape and the growing role of AI, cloud, and regulatory frameworks in shaping security strategies. With a diverse panel of industry experts and a highly engaged audience, the session turned into a dynamic exchange of ideas, experiences, and practical insights. </p><p>I totally enjoyed hosting and sincerely hope that everyone who attended the session enjoyed as much.</p><p>The panelists were:</p><ul><li><strong>Didier Samfat, PhD</strong><br>Cybersecurity Specialist | CISSP | CEH | CMNA</li><li><strong>Zeimm Auladin-Suhootoorah</strong><br>IT Manager at Gamma Civic Ltd</li><li><strong>Joe Van der Walt</strong><br>Founding Director at Kohde</li><li><strong>Sebastien Stormacq</strong><br>Principal Developer Advocate at Amazon Web Services</li></ul><figure class="kg-card kg-image-card"><img src="https://sysadmin-journal.com/content/images/2025/08/Axiz_Devcon-5.jpeg" class="kg-image" alt="Photo by Axiz Mauritius" loading="lazy" width="1200" height="800" srcset="/content/images/size/w600/2025/08/Axiz_Devcon-5.jpeg 600w, /content/images/size/w1000/2025/08/Axiz_Devcon-5.jpeg 1000w, /content/images/2025/08/Axiz_Devcon-5.jpeg 1200w" sizes="(min-width: 720px) 720px"></figure><p>Dr Samfat emphasized the need to foster a strong culture of security awareness within organizations, supported by regular maturity assessments to uncover potential blind spots. Building on this, Zeimm provided practical, real-world perspectives on securing complex infrastructure while striking the right balance between innovation and budget constraints. </p>
<!--kg-card-begin: html-->
<center>
  <blockquote class="twitter-tweet"><p lang="en" dir="ltr">.<a href="https://twitter.com/IshSookun?ref_src=twsrc%5Etfw">@IshSookun</a> asking questions about CERT-MU <a href="https://twitter.com/hashtag/mauritius?src=hash&amp;ref_src=twsrc%5Etfw">#mauritius</a> <a href="https://t.co/V345vpAeod">pic.twitter.com/V345vpAeod</a></p>&mdash; 𝗦 𝗠𝗼𝗼𝗻𝗲𝘀𝗮𝗺𝘆 (@sminmu) <a href="https://twitter.com/sminmu/status/1948269636375441868?ref_src=twsrc%5Etfw">July 24, 2025</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</center>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<center>
  <blockquote class="twitter-tweet"><p lang="en" dir="ltr">IT Manager at Gamma Civic Ltd commenting on the business case for cybersecurity <a href="https://twitter.com/hashtag/mauritiua?src=hash&amp;ref_src=twsrc%5Etfw">#mauritiua</a> <a href="https://t.co/MRkVfEQQ3i">pic.twitter.com/MRkVfEQQ3i</a></p>&mdash; 𝗦 𝗠𝗼𝗼𝗻𝗲𝘀𝗮𝗺𝘆 (@sminmu) <a href="https://twitter.com/sminmu/status/1948268529922318587?ref_src=twsrc%5Etfw">July 24, 2025</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</center>
<!--kg-card-end: html-->
<figure class="kg-card kg-image-card"><img src="https://sysadmin-journal.com/content/images/2025/08/Axiz_Devcon-2.jpeg" class="kg-image" alt="Joe Van der Walt (left), Ish Sookun (right), photo by Axiz Mauritius" loading="lazy" width="1200" height="800" srcset="/content/images/size/w600/2025/08/Axiz_Devcon-2.jpeg 600w, /content/images/size/w1000/2025/08/Axiz_Devcon-2.jpeg 1000w, /content/images/2025/08/Axiz_Devcon-2.jpeg 1200w" sizes="(min-width: 720px) 720px"></figure><p>Joe highlighted the developer’s critical role in embedding security from the outset and the growing responsibility of AI builders to protect user data and privacy.</p><p>Adding a broader lens, Sébastien explored cloud-native security, the shared responsibility model, and the impact of evolving regulatory frameworks such as GDPR and the Cyber Resilience Act.</p>
<!--kg-card-begin: html-->
<center>
  <blockquote class="twitter-tweet"><p lang="en" dir="ltr">.<a href="https://twitter.com/awscloud?ref_src=twsrc%5Etfw">@awscloud</a> principal developer advocate commenting on posting secret keys on GitHub <a href="https://twitter.com/hashtag/mauritius?src=hash&amp;ref_src=twsrc%5Etfw">#mauritius</a> <a href="https://t.co/e5B5RRG6XQ">pic.twitter.com/e5B5RRG6XQ</a></p>&mdash; 𝗦 𝗠𝗼𝗼𝗻𝗲𝘀𝗮𝗺𝘆 (@sminmu) <a href="https://twitter.com/sminmu/status/1948272869126856796?ref_src=twsrc%5Etfw">July 24, 2025</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</center>
<!--kg-card-end: html-->
<p>The room was filled to capacity, with an engaged and enthusiastic audience that actively participated by asking questions throughout the discussion. It's a shift from previous years’ format, as I invited audience interaction during the session rather than reserving all questions for a Q&amp;A at the end.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2025/08/Axiz_Devcon-4.jpeg" class="kg-image" alt="Panel discussion audience, room at full capacity, photo by Axiz Mauritius" loading="lazy" width="1200" height="800" srcset="/content/images/size/w600/2025/08/Axiz_Devcon-4.jpeg 600w, /content/images/size/w1000/2025/08/Axiz_Devcon-4.jpeg 1000w, /content/images/2025/08/Axiz_Devcon-4.jpeg 1200w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Photo by Axiz Mauritius</span></figcaption></figure>
<!--kg-card-begin: html-->
<center>
  <blockquote class="twitter-tweet"><p lang="en" dir="ltr">SWAN <a href="https://twitter.com/hashtag/mauritius?src=hash&amp;ref_src=twsrc%5Etfw">#mauritius</a> commenting of disclosing security incidents <a href="https://t.co/nzwmh1uaed">pic.twitter.com/nzwmh1uaed</a></p>&mdash; 𝗦 𝗠𝗼𝗼𝗻𝗲𝘀𝗮𝗺𝘆 (@sminmu) <a href="https://twitter.com/sminmu/status/1948274426102861893?ref_src=twsrc%5Etfw">July 24, 2025</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</center>
<!--kg-card-end: html-->
<p>Didier Adrien, IT Manager at SWAN, while commenting about security incidents in Mauritius, highlighted the importance of openly disclosing security incidents, stressing how such transparency benefits the broader community by fostering awareness, encouraging collaboration, and enabling collective learning to prevent similar threats.</p>]]></content:encoded>
        </item>
        <item>
            <title>Understanding Container Runtimes at the MSCC July Meetup</title>
            <link>https://sysadmin-journal.com/understanding-container-runtimes-mscc-july-meetup</link>
            <guid isPermaLink="true">https://sysadmin-journal.com/understanding-container-runtimes-mscc-july-meetup</guid>
            <pubDate>Sun, 10 Aug 2025 11:40:21 +0000</pubDate>
            <dc:creator>Ish Sookun</dc:creator>
            <category>MSCC</category>
            <category>Containers</category>
            <category>Meetup</category>
            <description>The Mauritius Software Craftsmanship Community (MSCC) hosts monthly meetups that bring together local developers, IT professionals, and tech enthusiasts to share knowledge, exchange ideas, and discuss emerging trends in software development, cloud technologies, DevOps, AI, and more.</description>
            <content:encoded><![CDATA[<p>The MSCC July meetup was hosted by Accenture. It was the first time that we were doing an MSCC meetup there. Their planning was meticulous and attendance on the day was strictly controlled based on the RSVP. We understood that Accenture adheres to strict company policies, a good practice especially about the RSVP attendance. The latter is often taken for granted — people who RSVP every month but never show up. 🙄 Meanwhile, there are also those who show up without RSVP'ing. They perhaps ignore how important RSVP is for the organisers to properly plan and manage a venue. Please consider being more serious about the RSVP button on the <a href="https://www.meetup.com/mauritiussoftwarecraftsmanshipcommunity/" rel="noreferrer">MSCC meetup.com</a> page.</p><p>On the day, i.e Saturday 12 July, I had a talk scheduled on "Container Runtimes."</p><p>It's been a while that I wanted to do this talk and dive into the lower realms of container management. We often talk about and demo container orchestration using high level tools, while not giving much attention to the underlying technologies. Therefore, my talk was like a primer on Linux namespaces and cgroups, followed by an introduction to container engines, mentioning the low-level and high-level ones.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2025/08/mscc-july-meetup-1.jpeg" class="kg-image" alt="Me preaching about open-source, photo credit: Arwin Neil Baichoo" loading="lazy" width="1280" height="854" srcset="/content/images/size/w600/2025/08/mscc-july-meetup-1.jpeg 600w, /content/images/size/w1000/2025/08/mscc-july-meetup-1.jpeg 1000w, /content/images/2025/08/mscc-july-meetup-1.jpeg 1280w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Me preaching about open-source, photo credit: Arwin Neil Baichoo</span></figcaption></figure><p>Before my talk, I had a very interesting discussion with Renghen. As he was going through my slide deck, he noticed that I mentioned <code>cgroups</code> and he pointed out that all the modern features that we enjoy in container orchestration have been enabled thanks to <code>cgroups v2</code> which was a complete overhaul of version 1, not just an update. Cgroup v2 provides more granular control over resource allocation, brought rootless features, simplifies resource management under one single unified hierarchy, among other improvements. I took note of that and included it when I spoke about control groups. </p><p>I shared a list of current actively developed container runtimes, among which I highlighted <code>youki</code> — a container runtime written in Rust. I co-maintain the <code>youki</code> package on the <a href="https://build.opensuse.org/package/show/Virtualization:containers/youki" rel="noreferrer">openSUSE Build Service</a> and I wanted to share how I am using <code>youki</code> as an experiment to dive deeper and learn more about the low-level workings of container management. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2025/08/DSC05516.JPG" class="kg-image" alt="The mandated weird angle taken by Arwin Neil Baichoo" loading="lazy" width="2000" height="1333" srcset="/content/images/size/w600/2025/08/DSC05516.JPG 600w, /content/images/size/w1000/2025/08/DSC05516.JPG 1000w, /content/images/size/w1600/2025/08/DSC05516.JPG 1600w, /content/images/size/w2400/2025/08/DSC05516.JPG 2400w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">The mandated weird angle taken by Arwin Neil Baichoo</span></figcaption></figure><p>Youki creates and starts a container but you will need a higher level container engine to actually manage your container resources. For that, I mentioned <a href="https://podman.io/" rel="noreferrer">Podman</a>. In fact, I asked how many in the room knew about Podman and to my surprise many raised their hands. It turns out that Podman is being heavily favoured over Docker by the development team at Accenture — something I was very glad to hear as a proponent of Podman since its inception. I did an <a href="https://sysadmin-journal.com/podman-101-at-middlesex-university-mauritius/" rel="noreferrer">introduction to Podman</a> at the Middlesex University in 2019, the same year the stable version of Podman was released. Since then, Podman has always been my demo tool when talking about containers.</p><p>I happily shared a few SUSE geeko plushies with the attendees that were able to answer questions that I asked about container runtimes at the end of my presentation.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://sysadmin-journal.com/content/images/2025/08/mscc-july-meetup-4.jpeg" class="kg-image" alt="The Group Picture taken by Mary Jane Kirstätter" loading="lazy" width="1200" height="675" srcset="/content/images/size/w600/2025/08/mscc-july-meetup-4.jpeg 600w, /content/images/size/w1000/2025/08/mscc-july-meetup-4.jpeg 1000w, /content/images/2025/08/mscc-july-meetup-4.jpeg 1200w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">The Group Picture taken by Mary Jane Kirstätter </span></figcaption></figure>]]></content:encoded>
        </item>
    </channel>
</rss>
