EP3: Native Kubernetes deployment is officially working in Coolify. But getting there meant wrestling with vicious race conditions.
Source: Dev.to
Introduction
If you missed Episode 2, we discovered that Coolify’s SSH‑native engine is surprisingly cluster‑friendly. The architecture wasn’t locked to Docker; it simply lacked a translation layer.
Episode 3 focused on building that translation layer and proving the concept, which turned into two massive, distinct implementation phases and one of the most stressful race conditions I’ve ever debugged—while backpacking across four countries.
Bootstrapping a Kubernetes Cluster
Before deploying an application, a Kubernetes cluster must exist. The official Coolify deployment has zero built‑in Kubernetes infrastructure, so I needed a way for users to spin up an environment directly from the UI.
After evaluating many options, I settled on K3s, an incredibly lightweight, production‑ready distribution that fits the servers Coolify typically runs on.
UI Integration
I integrated K3s directly into the frontend, adding UI components and backend logic that allow users to:
- Spin up a brand‑new K3s cluster from scratch on a server.
- Link securely to an existing Kubernetes cluster.
Phase 1 was a resounding success, providing the foundation for the next steps.
Deploying a Docker Image to K3s
The second phase was the grand finale: taking a standard Docker image (Nginx), deploying it as a service directly to the newly created K3s cluster, and exposing it via an Ingress route.
The translation script worked flawlessly—Docker manifests were converted into Kubernetes Deployments, Services, and Traefik Ingress rules. However, synchronizing the status UI proved challenging.
Synchronous vs. Asynchronous Deployment
- Synchronous approach: The core action waited for the cluster to finish deploying. This locked the PHP process and froze the Coolify frontend.
- Asynchronous approach: Switching to a background job made the UI responsive again, but introduced a new problem. The Application Status Checker immediately polled the K8s API, which—being eventually consistent—reported zero pods running while the pods were still being scheduled. The orchestrator mistakenly flagged the deployment as “Exited” or “Failed,” showing a red error state.
You cannot force an intrinsically asynchronous system (Kubernetes scheduling) to behave linearly against a strict synchronous status check. An empty resource list right after creation is an expected intermediate state, not a failure.
Solving the Race Condition
I introduced an intelligent two‑minute grace window into the status pipeline:
- If the checker polls an application within two minutes of an update and finds zero pods, it holds the UI status at Starting.
- Once the K8s API confirms the pods are healthy, the status flips to Running automatically.
This approach respects Kubernetes’ eventual consistency while keeping the UI accurate and responsive.
Outcome
The end‑to‑end “Docker Image → K8s” flow is now:
- Incredibly fast
- Fully observable
- Completely robust
I successfully installed K3s, defeated the deployment race conditions, and proved that native Kubernetes fits perfectly inside Coolify. With the automated systems handling eventual consistency, I could finally close my laptop and enjoy the rest of my tour.
Future Work
Next steps include:
- Deepening support for linking external production clusters
- Polishing features for robust availability
Stay tuned for the upcoming improvements!
References
- GitHub Issue: