<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Najeen Nepali</title>
    <description>The latest articles on DEV Community by Najeen Nepali (@najeen182).</description>
    <link>https://dev.to/najeen182</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4160144%2F9cb837b7-0a43-49f3-b5ea-82129c869c7d.jpg</url>
      <title>DEV Community: Najeen Nepali</title>
      <link>https://dev.to/najeen182</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/najeen182"/>
    <language>en</language>
    <item>
      <title>Fell in Love: Ephemeral Environments and DevEx</title>
      <dc:creator>Najeen Nepali</dc:creator>
      <pubDate>Tue, 20 Oct 2026 11:25:47 +0000</pubDate>
      <link>https://dev.to/najeen182/fell-in-love-ephemeral-environments-and-devex-1a21</link>
      <guid>https://dev.to/najeen182/fell-in-love-ephemeral-environments-and-devex-1a21</guid>
      <description>&lt;p&gt;&lt;em&gt;How a conference talk, a hardcoded branch, and too many deployment requests led me to ephemeral environments.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Remember how it started?&lt;br&gt;
I was at a conference, volunteering.&lt;br&gt;
Someone said "ephemeral," and I got hooked.&lt;br&gt;
Remember when I fell in love?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I do.&lt;/p&gt;

&lt;p&gt;April. NDC Sydney.&lt;/p&gt;

&lt;p&gt;I wasn't on stage, or even there as a regular attendee.&lt;/p&gt;

&lt;p&gt;I was volunteering.&lt;/p&gt;

&lt;p&gt;And somewhere in between, I ended up in a talk called:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Ephemeral Environments: Easier Said Than Spun"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And I fell for it.&lt;/p&gt;

&lt;p&gt;Not a person.&lt;/p&gt;

&lt;p&gt;An idea that could kill the most annoying sentence in my job.&lt;/p&gt;

&lt;p&gt;To explain why it hit so hard, I need to go back a bit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before we met
&lt;/h2&gt;

&lt;p&gt;For a long time, my favourite word at work was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Not now."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not because I didn't want to help.&lt;/p&gt;

&lt;p&gt;Because I was the only one who could do it.&lt;/p&gt;

&lt;p&gt;Our pipeline was simple.&lt;/p&gt;

&lt;p&gt;Build.&lt;/p&gt;

&lt;p&gt;Deploy.&lt;/p&gt;

&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;Two stages, and only for the branches that mattered. &lt;code&gt;develop&lt;/code&gt; went to dev. &lt;code&gt;main&lt;/code&gt; went to prod.&lt;/p&gt;

&lt;p&gt;It worked.&lt;/p&gt;

&lt;p&gt;Until a developer walked up to me with this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Can we deploy my branch?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And I said it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Not now."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But I'm not completely heartless.&lt;/p&gt;

&lt;p&gt;So, a bit later, I added a rule.&lt;/p&gt;

&lt;p&gt;If the branch is &lt;em&gt;this&lt;/em&gt; one, deploy it.&lt;/p&gt;

&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_BRANCH == "feature/the-one-you-asked-for"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A branch name, hardcoded in the pipeline.&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;p&gt;Next.&lt;/p&gt;




&lt;h2&gt;
  
  
  Then came the variables
&lt;/h2&gt;

&lt;p&gt;Hardcoding one branch was fine.&lt;/p&gt;

&lt;p&gt;Hardcoding the next one was annoying.&lt;/p&gt;

&lt;p&gt;So I got a little smarter.&lt;/p&gt;

&lt;p&gt;I swapped the branch name for a variable.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;DEFAULT_BRANCH&lt;/code&gt; equals this branch, deploy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_COMMIT_BRANCH == $DEFAULT_BRANCH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now anyone could trigger the pipeline by hand, pass in the branch, and deploy it.&lt;/p&gt;

&lt;p&gt;No new commit, no new rule.&lt;/p&gt;

&lt;p&gt;And this time I was smart enough not to do it myself.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Go to Run pipeline, set the variable to your branch, and do it yourself."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At the time, it felt like engineering.&lt;/p&gt;

&lt;p&gt;It was also about when I started hearing the word &lt;strong&gt;DevEx&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And I really believed I was doing it.&lt;/p&gt;

&lt;p&gt;Halfway, maybe.&lt;/p&gt;




&lt;h2&gt;
  
  
  Then it spread
&lt;/h2&gt;

&lt;p&gt;It wasn't one developer, or one project.&lt;/p&gt;

&lt;p&gt;The messages kept coming in.&lt;/p&gt;

&lt;p&gt;But the question had changed.&lt;/p&gt;

&lt;p&gt;It wasn't "Can you deploy my branch?" anymore.&lt;/p&gt;

&lt;p&gt;It was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How can I deploy my branch?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Progress.&lt;/p&gt;

&lt;p&gt;Kind of.&lt;/p&gt;

&lt;p&gt;So every time, the same little routine.&lt;/p&gt;

&lt;p&gt;Which project?&lt;/p&gt;

&lt;p&gt;Does that one have the variable yet?&lt;/p&gt;

&lt;p&gt;Go to CI/CD, Run pipeline, set &lt;code&gt;DEFAULT_BRANCH&lt;/code&gt;, wait for green.&lt;/p&gt;

&lt;p&gt;Then the next one.&lt;/p&gt;

&lt;p&gt;I'd stopped being the deploy button.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now I was the manual for the deploy button.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Back to that talk
&lt;/h2&gt;

&lt;p&gt;So there I was at NDC, listening to a talk about ephemeral environments.&lt;/p&gt;

&lt;p&gt;The gist: everyone loves the idea, few teams make it stick, and it's a trade between the coordination overhead of static environments and the cost of ephemeral ones.&lt;/p&gt;

&lt;p&gt;Coordination overhead.&lt;/p&gt;

&lt;p&gt;That one landed.&lt;/p&gt;

&lt;p&gt;That was me, explaining Run pipeline to one developer after another.&lt;/p&gt;

&lt;p&gt;And one more line I'd remember later: you'll need parameters and variables everywhere, and they'll frustrate you.&lt;/p&gt;

&lt;p&gt;I walked out thinking about my pipeline.&lt;/p&gt;

&lt;p&gt;None of the idea was new to the world.&lt;/p&gt;

&lt;p&gt;It was new to me.&lt;/p&gt;

&lt;p&gt;Every merge request gets its own environment.&lt;/p&gt;

&lt;p&gt;It comes up when the MR opens.&lt;/p&gt;

&lt;p&gt;It goes away when the MR closes.&lt;/p&gt;

&lt;p&gt;Nobody asks me.&lt;/p&gt;

&lt;p&gt;Nobody waits for me.&lt;/p&gt;

&lt;p&gt;Nobody types a branch name into a variable.&lt;/p&gt;

&lt;p&gt;And that's when it clicked.&lt;/p&gt;

&lt;p&gt;I'd been solving the right problem the wrong way.&lt;/p&gt;

&lt;p&gt;I'd stopped deploying for them.&lt;/p&gt;

&lt;p&gt;But they still needed me to tell them how.&lt;/p&gt;

&lt;p&gt;Remember when I fell in love?&lt;/p&gt;

&lt;p&gt;That was it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Easier said than spun
&lt;/h2&gt;

&lt;p&gt;So I tried it.&lt;/p&gt;

&lt;p&gt;One project.&lt;/p&gt;

&lt;p&gt;And I got lucky with the first one.&lt;/p&gt;

&lt;p&gt;It's a monorepo. Backend and frontend, in the same place.&lt;/p&gt;

&lt;p&gt;Nothing else to drag along.&lt;/p&gt;

&lt;p&gt;Before I wrote a single line of YAML, I had to check something with the developer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Is the API URL hardcoded in a &lt;code&gt;NEXT_&lt;/code&gt; variable anywhere?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because if the frontend only knows how to talk to one specific backend, every preview frontend ends up calling the same API.&lt;/p&gt;

&lt;p&gt;That's not a preview.&lt;/p&gt;

&lt;p&gt;That's a costume.&lt;/p&gt;

&lt;p&gt;The talk did warn me about variables.&lt;/p&gt;

&lt;p&gt;Once that was sorted, the setup was pretty simple.&lt;/p&gt;

&lt;p&gt;GitLab builds the images for the MR.&lt;/p&gt;

&lt;p&gt;Argo CD notices the MR and spins up a preview for it.&lt;/p&gt;

&lt;p&gt;Its own namespace, its own URL with the MR number in it, and its own fresh database on the shared dev Postgres server, with some demo data so it isn't empty.&lt;/p&gt;

&lt;p&gt;When the MR is merged or closed, Argo deletes the preview, and a cleanup step drops its database. The empty namespace Argo leaves behind gets swept up by a small hourly job.&lt;/p&gt;

&lt;p&gt;Open the MR. Test it. Close it. Gone.&lt;/p&gt;

&lt;p&gt;Nobody asks me.&lt;/p&gt;

&lt;p&gt;Honestly, I haven't fully tested it yet.&lt;/p&gt;

&lt;p&gt;But it exists, and it's already more honest than the variable trick.&lt;/p&gt;

&lt;p&gt;And yes, I know. I can already hear it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What if you have 10 MRs a day?"&lt;/p&gt;

&lt;p&gt;"What if one MR stays open for a week? A month?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fair.&lt;/p&gt;

&lt;p&gt;I don't have the answer yet.&lt;/p&gt;

&lt;p&gt;I know there are levers.&lt;/p&gt;

&lt;p&gt;I can cap how many MR environments run at the same time.&lt;/p&gt;

&lt;p&gt;I can look for stale branches and clean them up.&lt;/p&gt;

&lt;p&gt;Whether that's enough for &lt;em&gt;my&lt;/em&gt; setup?&lt;/p&gt;

&lt;p&gt;Still finding out.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bit I'm actually confused about
&lt;/h2&gt;

&lt;p&gt;The monorepo was the easy one.&lt;/p&gt;

&lt;p&gt;And even that one gets its own database, not its own Postgres server.&lt;/p&gt;

&lt;p&gt;Most of my apps aren't like that.&lt;/p&gt;

&lt;p&gt;I haven't built this part yet, but here's the scenario I keep staring at.&lt;/p&gt;

&lt;p&gt;An app that, to run at all, needs:&lt;/p&gt;

&lt;p&gt;a backend, a frontend, MinIO, a database, and sometimes a CMS.&lt;/p&gt;

&lt;p&gt;And the CMS usually brings its own backend and frontend, plus variables hardcoded for that specific app.&lt;/p&gt;

&lt;p&gt;Now say I'm in the frontend repo, and I open an MR.&lt;/p&gt;

&lt;p&gt;What does the preview spin up?&lt;/p&gt;

&lt;p&gt;Just the frontend, pointed at the backend that's already running in dev?&lt;/p&gt;

&lt;p&gt;That's fast.&lt;/p&gt;

&lt;p&gt;But now my "isolated" preview shares a backend, a database and a bucket with everyone else. And whatever the CMS has hardcoded still points wherever it points.&lt;/p&gt;

&lt;p&gt;Or all of it?&lt;/p&gt;

&lt;p&gt;Backend, MinIO, a fresh database, the CMS and all its pieces, for one frontend change?&lt;/p&gt;

&lt;p&gt;That's properly isolated.&lt;/p&gt;

&lt;p&gt;It's also slow and expensive.&lt;/p&gt;

&lt;p&gt;Exactly the kind of spin-up the talk warned about.&lt;/p&gt;

&lt;p&gt;I don't know yet.&lt;/p&gt;

&lt;p&gt;And I'd rather say that than pretend I've figured it out because a conference talk made it look clean.&lt;/p&gt;




&lt;h2&gt;
  
  
  What DevEx means to me
&lt;/h2&gt;

&lt;p&gt;Somewhere along the way, I stopped thinking of DevEx as tooling.&lt;/p&gt;

&lt;p&gt;For me, it's simple.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's control.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Control given to the developer to deploy their own branch and test it before it ever reaches a customer.&lt;/p&gt;

&lt;p&gt;A real environment where QA can poke at it.&lt;/p&gt;

&lt;p&gt;Where a PM can click through it and say "yes, that's what I meant."&lt;/p&gt;

&lt;p&gt;Without anyone having to ask me first.&lt;/p&gt;

&lt;p&gt;And, honestly?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's getting rid of the shitty daily jobs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The ones that aren't hard, or interesting.&lt;/p&gt;

&lt;p&gt;The ones that only exist because one person is standing between the developer and the deploy.&lt;/p&gt;

&lt;p&gt;That person was me.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where I am now
&lt;/h2&gt;

&lt;p&gt;One project, not fully tested.&lt;/p&gt;

&lt;p&gt;It's not done.&lt;/p&gt;

&lt;p&gt;It's not even close.&lt;/p&gt;

&lt;p&gt;But the direction finally feels right.&lt;/p&gt;

&lt;p&gt;For a long time, the answer to "can you deploy my branch?" was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Not now."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then it was a hardcoded rule.&lt;/p&gt;

&lt;p&gt;Then it was a variable, and me explaining how to use it.&lt;/p&gt;

&lt;p&gt;What I want it to be is no answer at all.&lt;/p&gt;

&lt;p&gt;Because nobody had to ask.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Easier said than spun.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But I'm spinning it anyway.&lt;/p&gt;




&lt;p&gt;🎧 Fell in Love — Blink-182&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://najeennp.com.np/devex" rel="noopener noreferrer"&gt;najeennp.com.np/devex&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>gitlab</category>
      <category>kubernetes</category>
      <category>devex</category>
    </item>
    <item>
      <title>Playing By Ear</title>
      <dc:creator>Najeen Nepali</dc:creator>
      <pubDate>Sat, 03 Oct 2026 16:44:55 +0000</pubDate>
      <link>https://dev.to/najeen182/playing-by-ear-3ja4</link>
      <guid>https://dev.to/najeen182/playing-by-ear-3ja4</guid>
      <description>&lt;p&gt;&lt;strong&gt;FEELING THIS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why moving to GitOps was the best infrastructure decision I've made: scattered Docker hosts, on-prem Kubernetes, a bash script talking to Portainer, and the day I stopped being the source of truth.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Before GitOps, our deployments were a bit like a band playing by ear.&lt;/p&gt;

&lt;p&gt;Everyone kind of knew the song.&lt;/p&gt;

&lt;p&gt;Nobody had the sheet music.&lt;/p&gt;

&lt;p&gt;And if you wanted to know how it actually went, you had to ask me.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it used to work
&lt;/h2&gt;

&lt;p&gt;At first, our apps lived on Docker.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;docker-compose.yml&lt;/code&gt; sat in the same repo as the application code.&lt;/p&gt;

&lt;p&gt;The pipeline built the image.&lt;/p&gt;

&lt;p&gt;We usually had two of them: a &lt;code&gt;develop&lt;/code&gt; image and a &lt;code&gt;main&lt;/code&gt; image.&lt;/p&gt;

&lt;p&gt;Then a script, triggered from CI/CD, told Portainer to redeploy.&lt;/p&gt;

&lt;p&gt;Green pipeline.&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;p&gt;Next.&lt;/p&gt;

&lt;p&gt;It worked.&lt;/p&gt;

&lt;p&gt;Until I needed to change one environment variable.&lt;/p&gt;

&lt;p&gt;One.&lt;/p&gt;

&lt;p&gt;And to do that, I had to run the whole pipeline again.&lt;/p&gt;

&lt;p&gt;Build the image.&lt;/p&gt;

&lt;p&gt;Push the image.&lt;/p&gt;

&lt;p&gt;Trigger the script.&lt;/p&gt;

&lt;p&gt;Redeploy.&lt;/p&gt;

&lt;p&gt;All of that for a single line of config that had nothing to do with the code.&lt;/p&gt;

&lt;p&gt;And that's if everything went right.&lt;/p&gt;

&lt;p&gt;Now imagine a developer messages me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Hey, can you add these two environment variables?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I add one.&lt;/p&gt;

&lt;p&gt;Run the pipeline.&lt;/p&gt;

&lt;p&gt;Build. Push. Trigger. Redeploy.&lt;/p&gt;

&lt;p&gt;Wait.&lt;/p&gt;

&lt;p&gt;Green.&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;p&gt;A little later:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Hey, the second one is still missing."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Right.&lt;/p&gt;

&lt;p&gt;Back to the pipeline.&lt;/p&gt;

&lt;p&gt;Build the same image again.&lt;/p&gt;

&lt;p&gt;Push it again.&lt;/p&gt;

&lt;p&gt;Trigger the script again.&lt;/p&gt;

&lt;p&gt;Redeploy again.&lt;/p&gt;

&lt;p&gt;And if I made a typo in the value?&lt;/p&gt;

&lt;p&gt;Same thing.&lt;/p&gt;

&lt;p&gt;Again.&lt;/p&gt;

&lt;p&gt;The code hadn't changed once.&lt;/p&gt;

&lt;p&gt;The image was identical every single time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Then came Kubernetes
&lt;/h2&gt;

&lt;p&gt;We had two on-prem clusters, dev and prod.&lt;/p&gt;

&lt;p&gt;And somewhere around them, 10 to 20 Docker hosts scattered across the infrastructure, each doing its own thing.&lt;/p&gt;

&lt;p&gt;On the Kubernetes side, the pipeline ran &lt;code&gt;helm&lt;/code&gt; directly.&lt;/p&gt;

&lt;p&gt;Which meant the pipeline needed cluster credentials.&lt;/p&gt;

&lt;p&gt;Which meant the kubeconfig lived in GitLab CI/CD variables.&lt;/p&gt;

&lt;p&gt;So our CI system could reach straight into the cluster and do whatever it liked.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where was the truth?
&lt;/h2&gt;

&lt;p&gt;This was the real problem.&lt;/p&gt;

&lt;p&gt;Not the scripts.&lt;/p&gt;

&lt;p&gt;Not Portainer.&lt;/p&gt;

&lt;p&gt;Not Helm.&lt;/p&gt;

&lt;p&gt;The question was simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What is actually supposed to be running right now?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And the honest answer was:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It depends who you ask.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The cluster had one version.&lt;/p&gt;

&lt;p&gt;Some wiki page had another.&lt;/p&gt;

&lt;p&gt;The compose file in the repo had a third.&lt;/p&gt;

&lt;p&gt;And the rest of it lived in my head.&lt;/p&gt;

&lt;p&gt;Anyone with access could change things.&lt;/p&gt;

&lt;p&gt;Sometimes someone hotfixed something directly.&lt;/p&gt;

&lt;p&gt;To be fair, drift wasn't a huge problem yet, because we were still early with Kubernetes. There wasn't much to drift.&lt;/p&gt;

&lt;p&gt;But the configuration was a mess.&lt;/p&gt;

&lt;p&gt;And onboarding a new service took a lot of work: copying files, writing scripts, wiring up credentials, remembering which wiki page was the correct one.&lt;/p&gt;

&lt;p&gt;We weren't reading from the sheet music.&lt;/p&gt;

&lt;p&gt;We were all playing by ear.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fleet first
&lt;/h2&gt;

&lt;p&gt;Funny thing is, GitOps didn't start with my own projects.&lt;/p&gt;

&lt;p&gt;It started with a message from another team, on January 29, 2024.&lt;/p&gt;

&lt;p&gt;I'd already used Kubernetes the way I described above: &lt;code&gt;kubectl&lt;/code&gt;, &lt;code&gt;helm upgrade&lt;/code&gt; straight from the pipeline, credentials in GitLab.&lt;/p&gt;

&lt;p&gt;This team was about to move a customer from Docker Swarm to Kubernetes, on a fresh on-prem setup with Rancher, RKE2 and Ceph.&lt;/p&gt;

&lt;p&gt;And then this line:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What's important to him is that all deployment processes follow gitops principles and are well documented to make debugging easier."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They also wanted a separate infra repo for all the configs not directly related to the application itself: reverse proxy, pipelines, the lot.&lt;/p&gt;

&lt;p&gt;A separate infra repo.&lt;/p&gt;

&lt;p&gt;I didn't even have that concept yet.&lt;/p&gt;

&lt;p&gt;Honestly, they wanted more than what I gave them.&lt;/p&gt;

&lt;p&gt;I was lazy.&lt;/p&gt;

&lt;p&gt;My knowledge was limited.&lt;/p&gt;

&lt;p&gt;And AI wasn't really a thing yet, so I couldn't just ask it to explain the hard parts to me.&lt;/p&gt;

&lt;p&gt;So I didn't go looking for the best tool.&lt;/p&gt;

&lt;p&gt;I went looking for the simplest one.&lt;/p&gt;

&lt;p&gt;That was Rancher Fleet.&lt;/p&gt;

&lt;p&gt;It came built into Rancher, and it was easy to set up without a lot of work.&lt;/p&gt;

&lt;p&gt;At least, I thought so.&lt;/p&gt;

&lt;p&gt;Because it didn't work.&lt;/p&gt;

&lt;p&gt;The repo was there.&lt;/p&gt;

&lt;p&gt;Fleet was there.&lt;/p&gt;

&lt;p&gt;Nothing deployed.&lt;/p&gt;

&lt;p&gt;Meanwhile, the developer on that team just wanted to ship to dev:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"If you give me a quick introduction on how to update the deployment on dev, I can also do it."&lt;/p&gt;

&lt;p&gt;"Or do you think the pipelines will be ready by then?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I didn't think so.&lt;/p&gt;

&lt;p&gt;So I offered the fallback.&lt;/p&gt;

&lt;p&gt;Connect Azure DevOps to the cluster.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;helm upgrade&lt;/code&gt; from the pipeline.&lt;/p&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;The exact thing the customer had asked us &lt;em&gt;not&lt;/em&gt; to do.&lt;/p&gt;

&lt;p&gt;Then came the fair question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"But what exactly is your issue with the GitOps approach again? Is it that the Azure repos don't work but the GitLab ones do?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Honestly?&lt;/p&gt;

&lt;p&gt;I didn't fully know.&lt;/p&gt;

&lt;p&gt;So I took the same repo, the same process, and pointed another cluster at it.&lt;/p&gt;

&lt;p&gt;It worked.&lt;/p&gt;

&lt;p&gt;So the problem wasn't my repo.&lt;/p&gt;

&lt;p&gt;It wasn't Azure.&lt;/p&gt;

&lt;p&gt;It wasn't GitOps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It was the Fleet version in Rancher.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All that time, and the fix was an upgrade.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's where it all started.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After that, I brought it to the rest of our projects.&lt;/p&gt;

&lt;p&gt;No big-bang migration.&lt;/p&gt;

&lt;p&gt;One project at a time, based on priority.&lt;/p&gt;




&lt;h2&gt;
  
  
  Then Argo CD
&lt;/h2&gt;

&lt;p&gt;I stayed on Fleet for a little over two years.&lt;/p&gt;

&lt;p&gt;What finally moved me wasn't a blog post or a comparison table.&lt;/p&gt;

&lt;p&gt;It was NDC Sydney, in April 2026.&lt;/p&gt;

&lt;p&gt;I met the Octopus Deploy team there, and they were all in on Argo.&lt;/p&gt;

&lt;p&gt;So I tried it.&lt;/p&gt;

&lt;p&gt;And I'll be honest about what hooked me.&lt;/p&gt;

&lt;p&gt;It wasn't some deep architectural reason.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It was the deployment notifications.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's it. That was the hook.&lt;/p&gt;

&lt;p&gt;But once I was in, I started to understand what I'd actually been missing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuration drift.&lt;/strong&gt; Argo shows you when the cluster doesn't match Git, which Fleet, at least the way I was using it, never really made obvious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Templating.&lt;/strong&gt; The way Argo templates applications is one of the best things about it.&lt;/p&gt;

&lt;p&gt;And then there was &lt;strong&gt;ApplicationSet&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;More on that one in a minute.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it looks like now
&lt;/h2&gt;

&lt;p&gt;The manifests live in a separate repo.&lt;/p&gt;

&lt;p&gt;One repo per project, a small monorepo holding the manifests and infrastructure definitions that project needs to run.&lt;/p&gt;

&lt;p&gt;It evolved over time:&lt;/p&gt;

&lt;p&gt;compose → Helm → infrastructure → and gradually, catalogs.&lt;/p&gt;

&lt;p&gt;Dev and prod are just folders.&lt;/p&gt;

&lt;p&gt;Mostly Helm. Some raw YAML. Kustomize has started showing up recently.&lt;/p&gt;

&lt;p&gt;The flow is simple:&lt;/p&gt;

&lt;p&gt;CI builds the image and runs the checks.&lt;/p&gt;

&lt;p&gt;A deployment stage commits the new image tag to the manifest repo.&lt;/p&gt;

&lt;p&gt;Argo sees the change and syncs it.&lt;/p&gt;

&lt;p&gt;No kubeconfig in GitLab.&lt;/p&gt;

&lt;p&gt;No script talking to Portainer.&lt;/p&gt;

&lt;p&gt;No pipeline reaching into the cluster.&lt;/p&gt;

&lt;p&gt;The pipeline writes to Git.&lt;/p&gt;

&lt;p&gt;Argo reads from Git.&lt;/p&gt;

&lt;p&gt;The cluster follows.&lt;/p&gt;

&lt;p&gt;And no, we don't "promote" images from dev to prod.&lt;/p&gt;

&lt;p&gt;Dev and prod can run genuinely different code, and some frontends bake environment variables in at build time, so the same image can't simply move forward.&lt;/p&gt;

&lt;p&gt;I'm still looking at whether we can make that work.&lt;/p&gt;

&lt;p&gt;But I'm not going to pretend we're doing it just because a conference talk says we should.&lt;/p&gt;




&lt;h2&gt;
  
  
  Things I had to unlearn
&lt;/h2&gt;

&lt;p&gt;Moving to GitOps wasn't just new tools.&lt;/p&gt;

&lt;p&gt;It was breaking a lot of old habits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;kubectl edit&lt;/code&gt; doesn't work anymore.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One day I wanted to quickly test something.&lt;/p&gt;

&lt;p&gt;Temporarily change an image tag.&lt;/p&gt;

&lt;p&gt;So I did what I'd always done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl edit deployment &amp;lt;app&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Changed the tag.&lt;/p&gt;

&lt;p&gt;Saved.&lt;/p&gt;

&lt;p&gt;Felt productive.&lt;/p&gt;

&lt;p&gt;A minute later, Argo reverted it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WTF.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then it clicked.&lt;/p&gt;

&lt;p&gt;Argo wasn't broken.&lt;/p&gt;

&lt;p&gt;It was doing exactly what I'd asked it to do: make the cluster look like Git.&lt;/p&gt;

&lt;p&gt;I'd just never had anything that actually held me to that before.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The code repo isn't the only repo.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before, all I knew was that whatever an app needed should live in the app's repo.&lt;/p&gt;

&lt;p&gt;Code, compose file, scripts, config. All of it.&lt;/p&gt;

&lt;p&gt;But the code and the configuration that runs the code are not the same thing.&lt;/p&gt;

&lt;p&gt;They change at different speeds.&lt;/p&gt;

&lt;p&gt;They're changed by different people.&lt;/p&gt;

&lt;p&gt;They break for different reasons.&lt;/p&gt;

&lt;p&gt;A separate repo for manifests sounded like extra work.&lt;/p&gt;

&lt;p&gt;It turned out to be the whole point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop answering from memory.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before, I knew where things were.&lt;/p&gt;

&lt;p&gt;That felt useful.&lt;/p&gt;

&lt;p&gt;It was actually the problem.&lt;/p&gt;

&lt;p&gt;And it's a hard habit to break, because answering from memory is faster.&lt;/p&gt;

&lt;p&gt;Every single time.&lt;/p&gt;

&lt;p&gt;So now when someone asks, I have to stop myself.&lt;/p&gt;

&lt;p&gt;Open the repo.&lt;/p&gt;

&lt;p&gt;Find the line.&lt;/p&gt;

&lt;p&gt;Send the link:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The answer you want is on this line."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Slower for me today.&lt;/p&gt;

&lt;p&gt;Faster for everyone after.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop building around Portainer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I was so obsessed with Portainer that I ended up writing Ansible playbooks &lt;em&gt;for&lt;/em&gt; Portainer.&lt;/p&gt;

&lt;p&gt;They created stacks.&lt;/p&gt;

&lt;p&gt;They redeployed the stacks.&lt;/p&gt;

&lt;p&gt;And all the credentials were in the repo itself, encrypted with Ansible Vault.&lt;/p&gt;

&lt;p&gt;At the time, it felt like engineering.&lt;/p&gt;

&lt;p&gt;Looking back, I had built a GitOps tool by hand.&lt;/p&gt;

&lt;p&gt;A worse one.&lt;/p&gt;




&lt;h2&gt;
  
  
  The gains
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;I can see everything.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One glance shows the whole project.&lt;/p&gt;

&lt;p&gt;What's deployed.&lt;/p&gt;

&lt;p&gt;Which version.&lt;/p&gt;

&lt;p&gt;Whether it matches Git.&lt;/p&gt;

&lt;p&gt;Remember that one environment variable that needed a full rebuild?&lt;/p&gt;

&lt;p&gt;Now it's a one-line commit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ApplicationSet.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We run a multi-tenant setup.&lt;/p&gt;

&lt;p&gt;Before, if something changed, I had to run the same &lt;code&gt;helm&lt;/code&gt; command once for every tenant.&lt;/p&gt;

&lt;p&gt;And keep the tenant list &lt;em&gt;somewhere&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Usually a mix of the pipeline, my notes and my memory.&lt;/p&gt;

&lt;p&gt;Now the tenant list is in Git.&lt;/p&gt;

&lt;p&gt;One trigger, and every tenant gets deployed.&lt;/p&gt;

&lt;p&gt;Want to bump the chart version?&lt;/p&gt;

&lt;p&gt;I don't open each tenant's Argo file anymore.&lt;/p&gt;

&lt;p&gt;I change one line.&lt;/p&gt;

&lt;p&gt;Every tenant gets it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image Updater.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This one I never found in Fleet.&lt;/p&gt;

&lt;p&gt;For one add-on repo, CI doesn't commit the image tag at all.&lt;/p&gt;

&lt;p&gt;It just builds the image and pushes it.&lt;/p&gt;

&lt;p&gt;Argo CD Image Updater watches the registry, sees the new image and updates the deployment itself.&lt;/p&gt;

&lt;p&gt;No tag commit.&lt;/p&gt;

&lt;p&gt;No extra pipeline stage.&lt;/p&gt;

&lt;p&gt;Push the image, and it shows up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anyone can deploy.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Developers can update ConfigMaps and check logs themselves.&lt;/p&gt;

&lt;p&gt;They don't have to wait for me.&lt;/p&gt;




&lt;h2&gt;
  
  
  The tradeoffs
&lt;/h2&gt;

&lt;p&gt;It's not free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's slower.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before, a green pipeline &lt;em&gt;was&lt;/em&gt; the deployment. No waiting.&lt;/p&gt;

&lt;p&gt;Now there's roughly a 4–5 minute gap while Argo picks up the change and syncs it.&lt;/p&gt;

&lt;p&gt;When you want something changed &lt;em&gt;right now&lt;/em&gt;, those minutes feel long.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It hides the deployment.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the one I didn't see coming.&lt;/p&gt;

&lt;p&gt;Developers see the deployment stage go green and assume it's deployed.&lt;/p&gt;

&lt;p&gt;But the green tick only means the tag was committed to Git.&lt;/p&gt;

&lt;p&gt;The sheet music changed.&lt;/p&gt;

&lt;p&gt;The band hasn't played it yet.&lt;/p&gt;

&lt;p&gt;It's not deployed until Argo says the app is healthy and the pods are actually running.&lt;/p&gt;

&lt;p&gt;To be fair, I had to learn that one too. On Docker, if the container was running, I called it healthy.&lt;/p&gt;

&lt;p&gt;GitOps made deploying so easy that a lot of people stopped wondering how it actually happens.&lt;/p&gt;

&lt;p&gt;And honestly, most people &lt;em&gt;want&lt;/em&gt; to know how it works.&lt;/p&gt;

&lt;p&gt;Most of them just have no idea yet.&lt;/p&gt;

&lt;p&gt;That one's on me to fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  Secrets: the bit I'm still fixing
&lt;/h2&gt;

&lt;p&gt;Secrets didn't move into Git.&lt;/p&gt;

&lt;p&gt;For a long time, they were applied by hand, with &lt;code&gt;kubectl apply&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And I paid for that.&lt;/p&gt;

&lt;p&gt;When I rebuilt the dev cluster, we were still on Fleet. Re-adding the projects to Fleet was easy, and the workloads came back.&lt;/p&gt;

&lt;p&gt;The secrets didn't.&lt;/p&gt;

&lt;p&gt;Finding them, rebuilding them, working out which values were current: that was the painful part.&lt;/p&gt;

&lt;p&gt;What was in Git came back easily.&lt;/p&gt;

&lt;p&gt;What wasn't was a hunt.&lt;/p&gt;

&lt;p&gt;That says more about GitOps than anything else in this post.&lt;/p&gt;

&lt;p&gt;So now we're slowly moving to Infisical.&lt;/p&gt;

&lt;p&gt;Partly because developers want to see the current value without asking me.&lt;/p&gt;

&lt;p&gt;Partly because I'm too lazy to log in and check for them.&lt;/p&gt;

&lt;p&gt;And mostly because I don't want to be the one person who has to be around every time a credential changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Once the configuration lives in Git, the next step gets much easier.&lt;/p&gt;

&lt;p&gt;Backstage templates.&lt;/p&gt;

&lt;p&gt;An internal developer platform.&lt;/p&gt;

&lt;p&gt;And, honestly, the fun part: being able to ask an AI to build a manifest &lt;em&gt;based on our company standard&lt;/em&gt;, and getting back something that actually fits.&lt;/p&gt;

&lt;p&gt;That only works because the standard exists.&lt;/p&gt;

&lt;p&gt;And the standard only exists because it's written down.&lt;/p&gt;




&lt;h2&gt;
  
  
  Was it worth it?
&lt;/h2&gt;

&lt;p&gt;So far, it's the best infrastructure decision I've made.&lt;/p&gt;

&lt;p&gt;Not because Argo CD is magic.&lt;/p&gt;

&lt;p&gt;Not because GitOps is fashionable.&lt;/p&gt;

&lt;p&gt;Because for the first time, there's one answer to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What is supposed to be running?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And the answer isn't me.&lt;/p&gt;

&lt;p&gt;It's in Git.&lt;/p&gt;

&lt;p&gt;We used to be a band playing by ear.&lt;/p&gt;

&lt;p&gt;Everyone knew roughly how the song went.&lt;/p&gt;

&lt;p&gt;Nobody could tell you exactly.&lt;/p&gt;

&lt;p&gt;Now there's sheet music.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Git is the sheet music. Argo is the one who notices when someone plays the wrong note.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And I finally get to stop humming the tune for everyone.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I got no regret right now.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I'm feeling this.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://najeennp.com.np/gitops.html" rel="noopener noreferrer"&gt;najeennp.com.np&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gitops</category>
      <category>kubernetes</category>
      <category>argocd</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
