<?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: Mr Bret</title>
    <description>The latest articles on DEV Community by Mr Bret (@awter12).</description>
    <link>https://dev.to/awter12</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%2F4083519%2F595ef1f8-7194-4a61-8f75-21ba31b3d7f6.jpg</url>
      <title>DEV Community: Mr Bret</title>
      <link>https://dev.to/awter12</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/awter12"/>
    <language>en</language>
    <item>
      <title>From Red to Green: Building a Jenkins Pipeline That Actually Ships Code</title>
      <dc:creator>Mr Bret</dc:creator>
      <pubDate>Sat, 22 Aug 2026 10:17:06 +0000</pubDate>
      <link>https://dev.to/awter12/from-red-to-green-building-a-jenkins-pipeline-that-actually-ships-code-5748</link>
      <guid>https://dev.to/awter12/from-red-to-green-building-a-jenkins-pipeline-that-actually-ships-code-5748</guid>
      <description>&lt;p&gt;There is a particular kind of satisfaction that comes from watching a pipeline go fully green after an entire day of failures. This is the story of building a Jenkins CI/CD pipeline that pulls code from a private repository, builds a Docker image, pushes it to a registry, and notifies a team on Slack, all without a single manual command once it is done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Goal&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The brief was simple to state and harder to execute: get Jenkins running locally, connect it to a private GitHub repository, containerize an application, automate the build and push to Docker Hub, and wire up Slack so the team knows the moment something breaks or succeeds. Five stages, one pipeline, zero manual intervention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setting the Foundation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Jenkins itself ran inside Docker, which introduced a subtlety many tutorials skip entirely. A container running Jenkins does not automatically know how to run other containers. The Docker command-line tool had to be installed inside that Jenkins container, and the container needed access to the host machine's Docker engine through a mounted socket. Getting this wrong produces a deceptively simple error message: command not found, as if Docker had never existed at all.&lt;/p&gt;

&lt;p&gt;The fix required understanding a core Docker concept properly. A running container's state can be captured and turned into a brand new, reusable image using a commit operation. Once the Docker command-line tool was installed and working inside the live container, that state was committed into a new image so the fix would survive even if the container had to be recreated later. This single lesson, that live changes inside a container are temporary unless explicitly saved, turned out to be the most reused&lt;br&gt;
piece of knowledge across the entire project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Teaching Jenkins to Trust a Private Repository&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Public repositories are forgiving. Private ones are not. Connecting Jenkins to a private GitHub repository meant generating a personal access token and registering it as a credential inside Jenkins, then referencing that credential explicitly inside the pipeline definition. One early misstep involved Jenkins looking for a branch called master when the actual repository used main as its default, resulting in a pipeline that could authenticate perfectly and still find nothing to build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writing the Pipeline Itself&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The heart of the project is a file called a Jenkinsfile, written in a structured syntax that describes a sequence of stages: clone the code, build the image, log into the registry, and push the image. Each stage runs only if the one before it succeeded, and the whole thing is version controlled alongside the application code itself.&lt;/p&gt;

&lt;p&gt;Two separate but related problems surfaced here. The first was a simple mismatch: the credential holding the Docker Hub username and password had its username field set incorrectly, causing every login attempt to fail with a message that looked like a network problem but was actually an identity problem. The second, more instructive issue was an access token created with read-only permissions instead of read and write, which allowed the pipeline to authenticate and even build the image successfully, only to fail at the very last step when it tried to push. The lesson here was that success at one stage says nothing about permissions further down the line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Making the Pipeline Talk&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A pipeline that runs silently is only half useful. The final piece was Slack integration, configured through a dedicated plugin and a webhook connected to a real Slack workspace. This introduced its own small trap: a placeholder channel name left unedited in the pipeline file, followed by a second attempt that mistakenly used the workspace name instead of an actual channel. Neither failure was loud. Both failed quietly with a generic notification error, which meant the fix required carefully reading exactly what value Jenkins was trying to send and comparing it against what actually existed inside the Slack workspace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Made the Difference&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;None of these problems were exotic. Every single one had a plain, findable cause once the actual error message was read carefully rather than assumed. A missing command, a wrong username, an underscoped permission, a placeholder value never replaced. Real infrastructure work rarely fails in dramatic, mysterious ways. It fails in small, specific, fixable ways that reward patience and careful reading over cleverness.&lt;/p&gt;

&lt;p&gt;By the end, a single push to the main branch triggered an entire chain reaction: code cloned, image built, image pushed to a public registry, and a message landing in Slack within seconds, all without anyone touching a keyboard after the initial commit. That is the entire point of continuous integration and continuous delivery. Not that things never break, but that when they do, the system tells you immediately, and fixing forward becomes the default instead of the exception.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>automation</category>
      <category>showdev</category>
      <category>jenkins</category>
    </item>
    <item>
      <title>Teaching a Container to Heal Itself: A Real Kubernetes Deployment Story</title>
      <dc:creator>Mr Bret</dc:creator>
      <pubDate>Sat, 22 Aug 2026 09:54:37 +0000</pubDate>
      <link>https://dev.to/awter12/teaching-a-container-to-heal-itself-a-real-kubernetes-deployment-story-1c7f</link>
      <guid>https://dev.to/awter12/teaching-a-container-to-heal-itself-a-real-kubernetes-deployment-story-1c7f</guid>
      <description>&lt;p&gt;There is a moment in every Kubernetes journey where the concept stops being theoretical. It happens the instant you delete a running pod on purpose, watch it disappear, and then watch a brand new one appear in its place without anyone telling it to. This is the account of getting a containerized application running on a real Kubernetes cluster, exposing it properly, securing its configuration, hardening it for failure, and finally teaching an existing pipeline to deploy it automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Starting With the Right Question&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The project began with a single running container and a much bigger question behind it: what happens when that container crashes at three in the morning with nobody watching? A single Docker container has no answer to that question. A Kubernetes cluster does.&lt;/p&gt;

&lt;p&gt;A local cluster was created using a lightweight tool that runs an entire Kubernetes control plane inside Docker itself, essentially a cluster pretending to be several machines while actually being one laptop. The first real obstacle appeared almost immediately: the application would not start, failing with a connection refused error on a port that seemed correct on paper. Tracing the problem back through the Dockerfile and then into the actual application code revealed the real issue. The port declared in the Dockerfile as documentation had nothing to do with the port the code actually listened on, which was determined by an environment variable with its own internal fallback value. The lesson was blunt and worth repeating: documentation inside a container tells you what someone intended, not what is actually happening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Giving the App a Front Door&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once the application was running and reachable through a temporary manual tunnel, the next milestone replaced that tunnel with something permanent. This required installing a separate piece of software dedicated entirely to routing incoming traffic by hostname, plus recreating the cluster from scratch with explicit networking rules, since local clusters on certain operating systems do not expose themselves to a browser without that extra step being taken up front. Once done, the application became reachable by a clean, memorable address instead of a raw port number that could change at any time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Separating Secrets From Code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Real applications need configuration that changes without requiring a rebuild and sensitive values that should never sit inside a public repository. This milestone introduced two new building blocks: one for ordinary settings that could live safely in plain text, and one specifically designed for values that needed to stay hidden. Both were wired into the running application as environment variables, invisible to anyone inspecting the image itself.&lt;/p&gt;

&lt;p&gt;The more important lesson here was procedural rather than technical. A real secret value, even a demonstration one, is dangerously easy to commit into version control out of pure habit. The fix involved excluding the sensitive file from tracking entirely and committing only a safe template in its place, a small discipline that matters far more once real credentials are involved rather than placeholder ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proving Resilience Instead of Assuming It&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is where the project stopped being an exercise and started feeling like engineering. Two production behaviors were configured and then, critically, tested rather than trusted on faith. A health check was added that periodically confirmed the application was genuinely responding, not merely that its process happened to still be alive. A second check confirmed the application was ready to accept traffic before ever being sent any. Multiple copies of the application were kept running simultaneously rather than just one.&lt;/p&gt;

&lt;p&gt;Then came the proof. One running copy was deleted deliberately, and a replacement appeared automatically within seconds, no human involved. Separately, a continuous stream of requests was aimed at the application while an update was triggered in the background, and every single request succeeded throughout the entire process, with old copies quietly retiring only after new ones had proven themselves ready. Watching that happen live is the single clearest argument for why this technology exists at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Closing the Loop&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The final piece connected everything back to the automated pipeline built earlier. Rather than manually applying configuration files by hand every time code changed, the existing pipeline was extended with one additional stage that deployed directly to the cluster, using a securely stored set of cluster credentials rather than anything hardcoded.&lt;/p&gt;

&lt;p&gt;This stage surfaced the most stubborn problem of the entire project. A configuration file meant to describe how to reach the cluster referenced an address that only made sense from the exact machine that generated it. Once that file was handed to a separate, isolated environment responsible for running the pipeline, that same address pointed nowhere useful. Solving it required understanding how two separate isolated environments could be placed on the same internal network and how to reference one from inside the other using its real internal address rather than a shortcut that only worked locally. It was the kind of problem that looks like magic until you understand exactly one detail, and then becomes completely obvious forever after.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What This Actually Proves&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;None of this was about mastering every feature Kubernetes offers. It was about taking one application that already existed, already had a working pipeline, and giving it the properties that separate a hobby project from something a team could actually depend on. Reachable by a stable address. Configured without secrets baked into its image. Resilient to individual failures. Updatable without dropping a single request. Deployed automatically the instant new code lands on the main branch.&lt;/p&gt;

&lt;p&gt;That combination, more than any single command or configuration file, is what production readiness actually means.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>automation</category>
      <category>kubernetes</category>
      <category>docker</category>
    </item>
  </channel>
</rss>
