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.
The Goal
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.
Setting the Foundation
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.
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
piece of knowledge across the entire project.
Teaching Jenkins to Trust a Private Repository
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.
Writing the Pipeline Itself
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.
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.
Making the Pipeline Talk
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.
What Made the Difference
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.
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.
Top comments (0)