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.
Starting With the Right Question
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.
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.
Giving the App a Front Door
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.
Separating Secrets From Code
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.
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.
Proving Resilience Instead of Assuming It
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.
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.
Closing the Loop
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.
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.
What This Actually Proves
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.
That combination, more than any single command or configuration file, is what production readiness actually means.
Top comments (0)