Skip to content
Docs
Release candidateThese docs are for Napwake 0.2.0-rc.9, a release candidate, and may change before it's released. Read the docs for the latest release.

Concepts

Dependencies

Keep a database awake while the apps that use it are, and wake it with them.

View .mdAsk Claude (opens claude.ai)Ask ChatGPT (opens chatgpt.com)
apiwaking uppostgreswaking upcachewaking up
A request wakes api, and the doorman holds it. Napwake wakes api's dependencies, postgres and cache, at the same time.

Databases, message brokers, caches and workers never see outside traffic. So their activity clock can run out while the workloads using them are busy. Napwake learns who needs what from each workload’s environment, or you declare it with dependsOn:

managedworkload.yaml
apiVersion: napwake.dev/v1alpha1
kind: ManagedWorkload
metadata:
name: api
namespace: preview-42
spec:
target: {kind: Deployment, name: api}
dependsOn:
- {kind: StatefulSet, name: postgres} # same namespace
- {namespace: messaging, kind: StatefulSet, name: nats} # another namespace
idlePolicy:
idleAfter: 1h
prediction: {} # the default, balanced
dependsOn
Goes on The workload that needs the other
Names The dependency’s Deployment or StatefulSet, not its ManagedWorkload
namespace Defaults to the ManagedWorkload’s own
As a label napwake.dev/depends-on: "statefulset/postgres, messaging/statefulset/nats" on the workload. See Opting in
Rule
A dependency doesn’t pause while anything that depends on it is awake Its own clock keeps running meanwhile
The last dependent pauses The dependency pauses on its next check, if its own clock has run out
Waking a workload wakes its dependencies Before it resumes, Napwake sets napwake.dev/last-activity on each dependency’s ManagedWorkload, the usual activity annotation wake
Seeing it kubectl describe shows it
It chains If Postgres depends on something, that wakes too
Everything wakes at once By default, so a chain doesn’t add up to cold starts in a row
Waking a dependency never holds up its dependent The dependent wakes, or is handed back or deleted, regardless
A dependency can’t be woken As when a policy engine won’t let Napwake annotate its ManagedWorkload. The dependent gets a DependencyWakeFailed Warning event

A dependency held awake says why:

HeldByDependents=True kept awake for preview-42/api, preview-43/api

waitForReady keeps the dependent from starting until the dependency serves. Set it for an app that fails if it starts first, such as an API that gives up after a few retries while its database replays its write-ahead log:

dependsOn:
- {kind: StatefulSet, name: postgres, waitForReady: true}
- {kind: Deployment, name: cache} # wakes at the same time

The dependent stays in Resuming, not scaled up:

WaitingForDependencies=True waiting for preview-42/statefulset/postgres (0 ready)
The dependency has It serves
Every replica Ready At once
Some replicas Ready, as with one pod crash-looping With those, so the dependent doesn’t wait for good
Some Ready, and Napwake manages it Once its own resume has finished, even short of every replica (ResumeIncomplete=True)
Some Ready, and Napwake doesn’t manage it Once the dependent has waited 2 minutes, the time a resume gives its other replicas
No replica Ready Never. It’s always waited for, up to the limit below
The wait
Limit The dependency Deployment’s progressDeadlineSeconds, 600 by default, when Kubernetes reports the rollout failed
Limit for a StatefulSet 10 minutes, as a StatefulSet has none
Counted from When the dependent began resuming
Why it’s long A slow start is why you set it
Why a limit Otherwise a dependency that’s up but never Ready would leave the dependent at zero for good
Never Ready, such as Crash-looping, or with an image that can’t be pulled
Past the limit The dependent starts anyway, with WaitingForDependencies=False, reason DependencyWontStart, and a Warning event naming the dependency
No wait, for an unmanaged dependency at zero Waiting can’t start it. It’s reported the same way
No wait, for one that waits for the dependent in turn Through waitForReady, directly or through others, as each would wait for the other
Then Reason DependencyCycle, and both start together
While waiting A managed dependency still paused is woken again every few seconds

Past the limit, the condition reads:

WaitingForDependencies=False not waiting for preview-42/statefulset/postgres (it isn't Ready after 10 minutes)
Situation What happens
The dependency has no ManagedWorkload It’s never paused, so there’s nothing to hold or wake
The dependency doesn’t exist DependencyNotFound=True on the dependent. Nothing is blocked, a waitForReady resume included
It’s outside watchNamespaces, or Napwake can’t read it DependencyNotFound=True, reason DependencyNotVisible, saying which. It isn’t held or woken, and nothing is blocked
A cycle in dependsOn, A on B and B on A Each would hold the other awake forever. Neither pauses, and both report DependencyCycle=True until it’s removed
A workload listed as its own dependency Ignored. It’s neither a cycle nor something to wait for
A namespace’s napwake.dev/depends-on names several workloads, such as statefulset/postgres, statefulset/redis Its other workloads take the list. The ones it names don’t, so postgres and redis don’t depend on each other
A named workload depends on another by its own annotation, as auth’s naming statefulset/postgres under a namespace naming deployment/auth That one doesn’t take the list either, so postgres doesn’t depend on auth
Learned dependencies in a loop, such as two services that call each other Not a cycle. A learned dependent doesn’t hold a workload it’s itself a dependency of
Then Each pauses when idle, and waking either wakes the other
kubectl napwake pause on a dependency in use Not paused, as for an idle pause. PauseRequest=False, reason HeldByDependents, and a HeldByDependents event naming the dependents. Pause them first
dryRun: true The hold applies and is reported, and nothing changes
A dry-run dependent that would have paused (Idle) It stops holding, so a chain’s would-be pauses all count
A dependency that pauses for real, held by a dry-run dependent It stays held, as the dependent’s pods are still up
A dependent managing nothing, as one being deleted, a duplicate (DuplicateTarget=True), or one whose target is gone (TargetAvailable=False, reason TargetNotFound) It holds nothing up. There’s no running workload to keep them up for
Many dependents awake at once HeldByDependents names the first 10, by namespace and name, and counts the rest

A dependency can be in another namespace if that namespace allows it. So preview environments can share a database or broker. List the namespaces allowed in its napwake.dev/dependents-from, or "*" for any:

Terminal window
kubectl annotate namespace shared napwake.dev/dependents-from="preview-42, preview-43"
What Rule
Why Without it, anyone who can write a ManagedWorkload, or a workload’s environment, in one namespace could keep another’s workloads awake
Not allowed A dependency, declared or learned, isn’t held, woken or waited for. The dependent reports it without saying whether it exists
Never Dependents never pause, scale down or delete a dependency. They only keep it awake or wake it
At most So one in another namespace can only keep it running
Holders HeldByDependents always names them by namespace

A dependency that isn’t allowed reads:

DependencyNotAllowed=True not in a namespace that allows dependents from preview-42 with napwake.dev/dependents-from, so not held or woken: shared/statefulset/postgres

Napwake learns dependencies from each workload’s environment, and from the requests that wake it. Each is held and woken exactly like a dependsOn, with no YAML to write.

From the environment
Read The environment of every workload Napwake manages, as kubectl napwake scan does
Counts A variable, literal or from a ConfigMap, holding an address that names a Service
In any form cluster DNS gives postgres, postgres.preview-42.svc.cluster.local, or a headless Service’s pod, postgres-0.postgres-hl
Followed to The workload the Service selects
Read again When the pod template changes, and hourly for ConfigMaps changed since
Never read Secrets. An address set only in a Secret isn’t learned from the environment
Reported A DependenciesLearned event when the set changes
The event says Whether each is woken with the workload. Only one Napwake manages, in a namespace that allows it, is
status.learnedDependencies
learnedDependencies:
from: 3f9c2a71d0e4b815
at: "2026-10-04T09:12:00Z"
dependencies:
- {namespace: preview-42, kind: StatefulSet, name: postgres, source: environment, via: PGHOST, address: postgres-0.postgres-hl}
- {namespace: preview-42, kind: StatefulSet, name: redis, source: wake}

Opt the dependency in too, to have them paused and woken together:

api: depends on preview-42/postgres (PGHOST), which isn't managed by Napwake, so it isn't woken with api: opt it in to have them paused and woken together

A request that wakes a workload teaches which workload sent it.

What How
How The doorman records where the request came from. The operator finds the pod that sent it
Learned If Napwake manages that pod’s workload, it depends on the one it woke, with source: wake
Catches Addresses in Secrets, built in code, or read from files, the first time they’re used
Teaches nothing A request from a workload Napwake doesn’t manage, such as an ingress controller
Nor A request from a pod on the node’s network, or a napwake.dev/last-request-from set by hand
Checked Only an address the doorman wrote counts. The API server’s field managers must show Napwake’s own set it
Admission The chart’s admission policy lets only the doorman set napwake.dev/last-request-from
Looked for in The woken workload’s namespace, and those its napwake.dev/dependents-from allows
Kept For 14 days after the last request that woke the dependency, through new reads of the environment
lastSeen Records that request, at most once a day
While it’s kept The dependency is held and woken with the workload
Renewed Not while it’s kept, as the workload’s requests no longer reach the doorman
Dropped Even in steady use, 14 days after it was learned or last renewed
Learned again From the next request that wakes the dependency. That costs one cold start, about once a fortnight
One-off calls Such as someone’s curl from a pod. Forgotten the same way, so they don’t tie two workloads together for good

A wrong learned link can only keep a workload awake longer, or wake it, never pause it. To drop one, list it in napwake.dev/ignore-dependencies on the workload or its ManagedWorkload. Separate entries with commas, as namespace/name or a name in its own namespace:

metadata:
annotations:
napwake.dev/ignore-dependencies: "redis, messaging/nats"

dependsOn still applies alongside, and is the only way to set waitForReady. kubectl napwake deps shows a workload’s links both ways, and where each came from.

Wake on request wakes a workload behind a normal Service by itself. Learned dependencies cover its environment, headless addresses included. dependsOn is for the rest:

  • waitForReady, so the first request doesn’t wait for the database too, or the app doesn’t fail starting before it.
  • Addresses not seen in use yet, from Secrets, code or files.
  • A workload behind no Service, which no address names.

kubectl napwake scan lists the dependencies in each workload’s environment, and what Napwake does with each:

When What Napwake does
Napwake manages both Woken and held with the workload that needs it, declared or learned
It doesn’t manage the dependency Never paused