DEV Community

Venkidesh Venu
Venkidesh Venu

Posted on

My App Was “Running”… But I Had No Idea If It Was Actually Working

There’s a moment every developer hits where things technically work… but you still don’t trust anything.

For me, that moment came when I moved our deployment system to the cloud.

As part of the project, we were building a platform where a user could upload a project, automatically build a Docker image, run it as a container, and get a live URL.

On paper, everything worked.
The VM was up.
The service was running.
Nothing had crashed.

And yet…

I had no idea if my system was actually working.


The Only Way I Could Check

At that point, I had built just one endpoint:

POST /upload
Enter fullscreen mode Exit fullscreen mode

This endpoint did everything:

  • Accept a zipped project
  • Build a Docker image
  • Run a container
  • Expose a port
  • Return a URL

So if I wanted to check whether my system was alive…

I had to deploy something.

Upload → Build → Run → Pray

That’s when it hit me:

I had built a system where the only way to check if it was alive… was to deploy an app.


Why I Added a /health Endpoint

I stopped and asked a simple question:

Why do I need a full deployment pipeline just to check if my service is alive?

I didn’t need builds.
I didn’t need containers.
I didn’t need logs.

I just needed a way to ask:

“Hey… are you alive?”

So I added:

GET /health
Enter fullscreen mode Exit fullscreen mode

And that tiny decision changed how I think about systems.


What is a Health Route?

A health route is a lightweight HTTP endpoint that tells whether your application is functioning correctly.

GET /health
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "status": "healthy"
}
Enter fullscreen mode Exit fullscreen mode

That’s it.

  • No business logic
  • No side effects
  • No heavy computation

Just a signal.

This endpoint isn’t for users.
It’s for systems, controllers, proxies, deployment pipelines, anything that needs to decide:

“Can I trust this service right now?”


Why This Matters: Running ≠ Working

Before this, I assumed:

Container started → App is ready

But that’s not true.

A container can be “running” while:

  • the app is still initializing
  • the database isn’t connected
  • dependencies are failing
  • something is internally broken

Without a health check, your platform is guessing.

With a health check, your platform knows.

That difference is what separates a toy system from a reliable one.


What Happens During Deployment

When an app starts, it doesn’t become ready instantly. It goes through a sequence:

  1. Container starts
  2. Application boots
  3. Dependencies initialize
  4. Database connects
  5. Server starts listening
  6. Finally ready

Without health checks, most systems assume:

Container started → Ready

Which leads to users hitting an app that isn’t ready yet.


With a Health Check

Instead of guessing, the system becomes smarter:

an automated container deployment process with health checks and traffic routing.

Now only ready applications receive traffic.

This makes deployments far more stable.


Liveness vs Readiness (The Real Upgrade)

Once you start thinking about health properly, you realize one endpoint isn’t enough.

There are actually two different questions:


Liveness → “Are you alive?”

  • Checks if the app process is still running
  • If this fails → restart the container
/health/live
Enter fullscreen mode Exit fullscreen mode

Readiness → “Are you ready?”

  • Checks if the app can handle real traffic
  • If this fails → don’t send traffic (but don’t kill it)
/health/ready
Enter fullscreen mode Exit fullscreen mode

Simple Analogy

  • Liveness = Student is in class
  • Readiness = Student studied for the exam

Just because something is running…
doesn’t mean it’s ready.


The Smallest Fix That Made a Big Difference

After all the confusion and overengineering, the actual solution was just a few lines of code.

Here’s how it looks in Go, but the idea is the same in any backend.

package main

import (
    "encoding/json"
    "net/http"
    "time"
)

var startTime = time.Now()

type HealthResponse struct {
    Status string  `json:"status"`
    Uptime float64 `json:"uptime_seconds"`
}

func HealthHandler(w http.ResponseWriter, r *http.Request) {
    response := HealthResponse{
        Status: "healthy",
        Uptime: time.Since(startTime).Seconds(),
    }

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(response)
}

func main() {
    http.HandleFunc("/health", HealthHandler)
    http.ListenAndServe(":8080", nil)
}
Enter fullscreen mode Exit fullscreen mode

No orchestration.
No complexity.
Just a tiny endpoint that tells the truth.


What Changed After This

With this one route, I could:

  • Instantly verify if my system is running
  • Avoid unnecessary deployments just for testing
  • Make my platform smarter about when to trust an app

It’s a small addition.

But it completely changes how your system behaves.


Best Practices (Quick Notes)

  • Keep it lightweight → this endpoint is called frequently
  • Use proper status codes

    • 200 → healthy
    • 503 → not ready
  • Separate liveness and readiness

  • Avoid over-checking dependencies

  • Always use timeouts (never let health checks hang)


Final Thought

The /health endpoint is probably the smallest route in your application.

But it might also be the most important one.

Because it answers the one question your entire system depends on:

“Can I trust this service right now?”

Top comments (0)