Production APIs go dark in ways that don't trip your tests: a database connection pool exhausted at 2 AM, a goroutine leak that slowly starves your workers, a misconfigured Docker healthcheck that kills the container after a restart. Vigilmon catches these from outside your infrastructure by actively probing your service every minute and alerting you within seconds of the first failure.
In this guide you'll wire a Go Gin API into Vigilmon for uptime monitoring, goroutine heartbeats, and alert routing — and set up a Docker deployment with proper health check configuration.
What You'll Build
- A
/healthendpoint in Gin that checks the database and reports status - A Vigilmon HTTP monitor pointed at your API
- A background goroutine heartbeat that pings Vigilmon every N minutes
- A
Dockerfilewith a properHEALTHCHECKdirective - Graceful shutdown so in-flight requests finish before the process exits
- Email and Slack webhook alerts
Prerequisites
- Go 1.21+
- A running Gin project (or follow along from scratch)
- A free Vigilmon account
Step 1: Add the Health Endpoint
A health endpoint gives Vigilmon (and Docker) a reliable signal that your service is alive and ready. Return structured JSON so that assertion rules on Vigilmon can check the body — not just the status code.
// internal/health/handler.go
package health
import (
"context"
"database/sql"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
type Handler struct {
DB *sql.DB
}
type Response struct {
Status string `json:"status"`
Timestamp time.Time `json:"timestamp"`
Checks map[string]string `json:"checks"`
}
func (h *Handler) Check(c *gin.Context) {
checks := map[string]string{}
overall := "ok"
// Ping the DB with a tight deadline so a hung connection doesn't stall the check.
ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second)
defer cancel()
if err := h.DB.PingContext(ctx); err != nil {
checks["database"] = "error: " + err.Error()
overall = "degraded"
} else {
checks["database"] = "ok"
}
statusCode := http.StatusOK
if overall != "ok" {
statusCode = http.StatusServiceUnavailable
}
c.JSON(statusCode, Response{
Status: overall,
Timestamp: time.Now().UTC(),
Checks: checks,
})
}
Register it in your router setup:
// cmd/api/main.go
package main
import (
"database/sql"
"log"
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/lib/pq"
"yourapp/internal/health"
)
func main() {
db, err := sql.Open("postgres", "postgres://user:pass@localhost/mydb?sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close()
r := gin.Default()
// Health endpoint — no auth middleware, reachable by Vigilmon
healthHandler := &health.Handler{DB: db}
r.GET("/health", healthHandler.Check)
// ... rest of your routes ...
r.Run(":8080")
}
Test it:
curl -s http://localhost:8080/health | jq .
{
"status": "ok",
"timestamp": "2025-06-29T10:00:00Z",
"checks": {
"database": "ok"
}
}
Step 2: Add the Vigilmon HTTP Monitor
- Log in to Vigilmon and click New Monitor → HTTP.
- Enter
https://your-domain.com/healthas the URL. - Set the interval to 60 seconds (free) or 30 seconds (paid).
- Under Assertions, add:
- Status code equals
200 - Response body contains
"status":"ok"
- Status code equals
- Save.
Vigilmon now checks your service from multiple regions and fires an alert within seconds if it starts returning errors or timing out.
Step 3: Configure Alerts
In the Vigilmon dashboard, go to Alerts → Channels.
Email: Add your on-call address. Vigilmon sends a failure alert within 30 seconds of the first failed check and a recovery notification when the service comes back.
Slack: Click Add Channel → Slack, paste your incoming webhook URL, and save. Route all CRITICAL monitors to both channels via Alerts → Routing.
Step 4: Goroutine Heartbeat for Background Workers
HTTP uptime checks verify your API is responding, but they won't catch a background goroutine that silently stopped running. Use a Vigilmon heartbeat monitor to detect this.
- In Vigilmon, go to New Monitor → Heartbeat.
- Set the expected period (e.g., 5 minutes).
- Copy the ping URL (
https://vigilmon.online/ping/YOUR_ID).
Now ping it from your background goroutine using time.Ticker:
// internal/worker/processor.go
package worker
import (
"context"
"log"
"net/http"
"time"
)
const vigilmonHeartbeat = "https://vigilmon.online/ping/YOUR_HEARTBEAT_ID"
func RunProcessor(ctx context.Context) {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
httpClient := &http.Client{Timeout: 5 * time.Second}
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
if err := processJobs(); err != nil {
log.Printf("processor error: %v", err)
continue // Don't ping on failure — let Vigilmon detect the miss
}
// Signal success
if _, err := httpClient.Get(vigilmonHeartbeat); err != nil {
log.Printf("vigilmon ping failed: %v", err)
}
}
}
}
Start the worker in main.go:
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go worker.RunProcessor(ctx)
If the goroutine panics and is never restarted, Vigilmon will alert you after one missed interval.
Step 5: Docker Deployment with HEALTHCHECK
Add a HEALTHCHECK instruction to your Dockerfile so Docker itself knows when to restart a container and when to mark it unhealthy:
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /api ./cmd/api
FROM alpine:3.19
RUN apk --no-cache add ca-certificates curl
WORKDIR /root/
COPY --from=builder /api .
EXPOSE 8080
# Docker will probe /health every 30s.
# 3 consecutive failures → container status = "unhealthy" → restart policy kicks in.
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
CMD ["./api"]
With restart: unless-stopped in your docker-compose.yml, Docker will restart an unhealthy container automatically. Vigilmon catches the window of unavailability and alerts you even when Docker recovers on its own.
Step 6: Graceful Shutdown
Graceful shutdown prevents in-flight requests from being cut off when you deploy a new version. Wire it up alongside the health endpoint:
// cmd/api/main.go
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// ... register routes ...
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
// Start the server in a goroutine
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %v", err)
}
}()
// Wait for SIGINT or SIGTERM (sent by Docker, Kubernetes, systemd, etc.)
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("shutting down gracefully")
// Give in-flight requests 10 seconds to finish
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("server forced to shutdown:", err)
}
log.Println("server exited")
}
During a rolling deploy your old container receives SIGTERM, drains its requests in ≤10 seconds, and exits cleanly. Vigilmon sees a brief gap at most and won't fire a false-positive alert if the new container comes up within the check interval.
Step 7: Verify End-to-End
- Confirm the Vigilmon dashboard shows the monitor as UP.
- Return a
503from your health handler temporarily and verify an alert arrives. - Restore the
200and confirm the recovery notification.
To simulate a database failure locally:
// Intentionally break the DB URL
db, _ := sql.Open("postgres", "postgres://bad:creds@localhost/none")
The health check returns 503 with "database": "error: ...", and Vigilmon catches it within the next check interval.
Production Checklist
- [ ]
/healthroute registered and returning200with JSON body - [ ] DB
PingContextusing a2sdeadline to avoid hanging checks - [ ] Vigilmon HTTP monitor with status code + body assertions
- [ ] Email and Slack alert channels configured
- [ ] Goroutine heartbeat monitor for background workers
- [ ]
HEALTHCHECKinDockerfile - [ ] Graceful shutdown wired to
SIGTERM - [ ] Health endpoint excluded from auth middleware
Summary
You now have a Go Gin API that:
- Exposes a structured
/healthendpoint with active dependency checks - Is probed every minute by Vigilmon from multiple regions
- Fires Slack and email alerts within seconds of a failure
- Runs background goroutines with heartbeat monitoring
- Deploys cleanly with Docker health checks and graceful shutdown
The setup takes under 30 minutes and runs on Vigilmon's free tier. Your on-call rotation gets an alert in Slack before any user files a bug report.