Putnami
DocsGitHub

Licensed under FSL-1.1-MIT

Getting Started
Concepts
How To
Build A Web App
Build An Api Service
Share Code Between Projects
Configure Your App
Add Persistence
Add Authentication
Add Background Jobs
Develop With Ai
Structure Business Logic With Di
Upgrade Putnami
Principles
Tooling & Workspace
Workspace
Cli
Jobs & Caching
Extensions
Templates
Error Handling
Frameworks
Typescript
ExtensionOverviewWebReact RoutingForms And ActionsStatic FilesApiErrors And ResponsesConfigurationLoggingHttp And MiddlewareDependency InjectionPlugins And LifecycleSessionsAuthPersistenceDocumentEventsStorageCachingWebsocketsTestingHealth ChecksTelemetryProto GrpcSmart ClientSchemaPlatform Endpoints
Go
ExtensionOverviewHttpDependency InjectionPlugins And LifecycleConfigurationSecurityPersistenceErrorsEventsStorageCachingLoggingTelemetryGrpcService ClientsValidationOpenapiTestingPlatform Endpoints
Python
Extension
Platform
Ci
  1. DocsSeparator
  2. FrameworksSeparator
  3. GoSeparator
  4. Platform Endpoints

Platform endpoints

go.putnami.dev/platform mounts the standard operational HTTP surface every workload needs — liveness, readiness, version, and (optionally) pprof — and auto-discovers per-plugin probes from the application's module tree.

It replaces the per-workload boilerplate (and the legacy http.NewHealthPlugin()'s single /_/health endpoint) with one plugin you opt in to.

Plugin setup

import (
    "go.putnami.dev/app"
    "go.putnami.dev/http"
    "go.putnami.dev/platform"
)

server := http.NewServerPlugin(http.ServerConfig{Port: 8080})
platformPlugin := platform.NewPlugin(platform.Config{
    Version: platform.VersionInfo{Name: "my-service", Version: "1.0.0"},
})
platformPlugin.RegisterOn(server)

a := app.New("my-service").
    Use(server).
    Use(platformPlugin)
a.ListenAndServe()

Endpoints

Path Purpose
/livez Lightweight liveness. Returns 200 {"status":"ok"} whenever the handler can run. No probes, no flags. Safe to use as a Kubernetes liveness probe.
/healthz Liveness aggregate. 200 when running and every app.HealthChecker probe passes. 503 {"status":"unavailable"} before Start and after Stop. 503 {"status":"degraded","checks":{…}} when any probe fails.
/readyz Readiness aggregate. Same shape as /healthz, driven by app.ReadinessChecker probes. The endpoint to use as a Kubernetes readiness probe.
/version Build metadata as JSON. Caller-supplied VersionInfo wins; empty fields fall back to runtime/debug.ReadBuildInfo.
/debug/pprof/* net/http/pprof index, named profiles (heap, goroutine, allocs, …) and on-demand collectors (profile, trace, cmdline, symbol). Disabled by default.

Default mount is root. Set Config.Prefix to namespace (Prefix: "/_" → /_/healthz, …).

Capability interfaces

The platform plugin discovers two interfaces by walking the module tree from the root and registers each implementation under its Name().

// in go.putnami.dev/app

type HealthChecker interface {
    Plugin
    CheckHealth(ctx context.Context) error
}

type ReadinessChecker interface {
    Plugin
    CheckReadiness(ctx context.Context) error
}
Interface Semantics Failure consequence (k8s)
HealthChecker Dependency is broken in a way only a restart fixes (liveness) Pod restart
ReadinessChecker Dependency is temporarily down — drain traffic but don't restart Traffic drained, no restart

A single plugin can implement both: CheckHealth contributes to /healthz, CheckReadiness contributes to /readyz.

Implementations must be safe to call concurrently and respect context cancellation. The plugin enforces a per-request Config.ProbeTimeout (default 5s) so a hung probe can't stall the endpoint.

Built-in contributor: database.Plugin implements app.HealthChecker — pool.Ping shows up under the database key on /healthz automatically when both plugins are mounted.

Explicit probes

For probes not owned by a plugin (an external URL, an ad-hoc check), register directly:

platformPlugin.AddHealthChecker("upstream", func(ctx context.Context) error {
    return checkUpstreamURL(ctx, "https://example.com/health")
})

platformPlugin.AddReadinessChecker("warm", func(ctx context.Context) error {
    if !cacheWarmed.Load() {
        return errors.New("cache warming")
    }
    return nil
})

Explicit registrations win over auto-discovered probes of the same name — useful for overriding a plugin-provided probe in a specific environment.

Configuration

type Config struct {
    Prefix       string        // path prefix (default: "" → root)
    EnablePprof  bool          // expose /debug/pprof/* (default: false)
    Version      VersionInfo   // /version payload
    ProbeTimeout time.Duration // per-probe timeout (default: 5s)
}

type VersionInfo struct {
    Name      string `json:"name,omitempty"`
    Version   string `json:"version,omitempty"`
    SHA       string `json:"sha,omitempty"`
    Branch    string `json:"branch,omitempty"`
    BuildTime string `json:"buildTime,omitempty"`
}

Build metadata

For production builds, embed version info at link time:

var version = "dev"
var commit = ""

platform.NewPlugin(platform.Config{
    Version: platform.VersionInfo{
        Name:    "my-service",
        Version: version,
        SHA:     commit,
    },
})

Build with go build -ldflags "-X main.version=1.2.3 -X main.commit=$(git rev-parse HEAD)". Leaving Version empty falls back to runtime/debug.ReadBuildInfo (module path, vcs.revision, vcs.time when built with -buildvcs).

pprof

/debug/pprof/* exposes Go's runtime profiler. Off by default — profiles reveal heap layout and goroutine details, and the CPU/trace collectors are expensive.

platform.NewPlugin(platform.Config{EnablePprof: true})

Do not expose pprof on a public port. Mount the platform plugin on a separate admin server, or restrict access via a sidecar / network policy.

Migrating from http.NewHealthPlugin()

The legacy http.HealthPlugin (single /_/health endpoint) still works and now also auto-discovers app.HealthChecker implementations. To get the full operational surface:

// before
a.Use(server).Use(http.NewHealthPlugin())

// after
platformPlugin := platform.NewPlugin(platform.Config{Prefix: "/_"}) // keep /_ namespace
platformPlugin.RegisterOn(server)
a.Use(server).Use(platformPlugin)

/_/health becomes /_/healthz, and you also get /_/livez, /_/readyz, /_/version. Drop the Prefix for plain k8s-style paths (/healthz, /livez, …). Don't mount both — they overlap on probe registration.

On this page

  • Platform endpoints
  • Plugin setup
  • Endpoints
  • Capability interfaces
  • Explicit probes
  • Configuration
  • Build metadata
  • pprof
  • Migrating from http.NewHealthPlugin()