How it works
One room, through a day.
Napwake pauses a workload once it’s idle, and wakes it when it’s wanted again. Each door below is a moment in one room’s day, and opens its chapter.
1 Pause on use
It pauses what’s idle.
No timetable decides it. Napwake keeps an activity clock for every workload it manages, the last time anything showed it was in use. When the clock is older than idleAfter, one hour by default, the workload is paused. Any single sign of activity resets it.
What counts as activity
| Source | Counts when | Needs |
|---|---|---|
| CPU | Above cpuThreshold of what the pods request, 10% by default. Set it per workload, per namespace or for the cluster | metrics-server |
| A deploy | The pod template changes: a new image, an environment variable | nothing |
| A request | The doorman held a request for it while it was paused | nothing |
napwake.dev/last-activity | Your own tooling sets a newer time, say a developer portal when someone opens an environment | your tooling |
napwake.dev/active-until | A time in the future, to hold it awake, say for a demo | your tooling |
| A Prometheus query | Any query you give it, such as request rate, returns more than zero | Prometheus |
- Every source is checked and the most recent wins, so a worker with real work and no traffic isn’t paused.
- Memory isn’t a source, since it stays allocated whether or not anyone uses the workload.
- It never pauses a workload it can’t measure, and there’s no learning period. A new workload can pause after its first idle hour.
Opting in
A label on a workload or a whole namespace, with settings as annotations, in the manifests or Helm values you already keep in Git. Start in dry-run. It measures what it would pause and free, and never pauses anything. It can’t see requests, so a workload used with little CPU looks idler than it is. A Prometheus query on its request rate at the ingress fixes that.
# deployment.yaml
metadata:
name: my-api
namespace: dev
labels:
napwake.dev/managed: "true"
annotations:
napwake.dev/dry-run: "true"
napwake.dev/idle-after: "1h"Then kubectl napwake enable my-api -n dev ends dry-run, and it pauses after idleAfter with no sign of use. Idle detection in the docs has every rule.
idleAfter, so its lights go out. pr-42 is in use, so it stays awake.2 How it wakes
The doorman wakes it on a request, or asks first.
A paused workload has no pods, so nothing would answer its Service. Its Services point at the doorman instead. Behind a VPN, single sign-on or an allowlist, the first request wakes it. On a public URL, set it to ask, and only a person’s click wakes it.
- The route. When a workload pauses, the operator adds an EndpointSlice to each of its Services, pointing at the doorman. The doorman is a small proxy, its own Deployment of two replicas beside the operator. The Service, its ClusterIP and its DNS name don’t change.
- The door. A connection reaches the doorman. Behind a VPN, once the caller has sent something, he holds it and wakes the workload. On a public URL set to ask, he waits for a person’s click instead. A wake takes the workload’s dependencies with it, such as the database it uses.
- The hand-over. As soon as a pod is Ready, by its readiness probe, a held connection goes through, with the bytes the caller already sent.
- Out of the way. Once the workload is running, the doorman’s EndpointSlices go and traffic reaches the pods directly again.
Waking on a request is the default, with nothing to configure. Asking first is napwake.dev/wake: ask on a workload or its namespace, or defaults.wake: ask in Helm for the whole cluster. Napwake can’t tell a public URL from a private one, so the setting is yours.
Behind your VPN or SSO
The first request wakes it.
- Once a caller sends something, the doorman holds the connection and wakes the workload. It works on TCP, so it holds HTTP, gRPC, Postgres and Redis alike.
- A caller that connects and sends nothing for 3 seconds wakes it too, as for MySQL, where the server speaks first.
- It holds a connection until a pod is Ready, for up to
wake.maxWait. That’s 2 minutes by default, and at most an hour. After that, it closes the connection, and the wake goes on. - A browser gets the waking page at once. It reloads every 3 seconds, and the first reload after a pod is Ready reaches the app. It names no namespace, workload or Service.
- Known probes, scrapers and uptime monitors, such as kube-probe, Prometheus, Datadog and Pingdom, get a
503. A TCP health check that connects and closes is closed. A request for a hidden file, such as/.env, gets a404. None of them wake it.
On a public URL
With napwake.dev/wake: ask, a person wakes it.
- On the internet, most requests are robots, and many pass for browsers. Once an environment has its own HTTPS certificate, its hostname is in public certificate logs within minutes. Left on the default, it would wake every few minutes.
- So set it to ask, and the doorman asks first. A browser gets the asleep page, kept out of search engines. Loading it wakes nothing.
- Wake it sends a token signed for that workload and that pause, good for 10 minutes. Only that wakes it, and the waking page carries on.
- A click wakes it at most once a minute. A bot that clicks is rare. Each wake it causes keeps the workload up for at least
idleAfter. - Anything else is turned away, and wakes nothing. Over HTTP it gets a
503that says how to wake it. Known probes are answered as behind a VPN.
Who wakes it, at each door
| The caller, while it’s asleep | Behind your VPN or SSO | On a public URL, set to ask |
|---|---|---|
| A person, in a browser | Wakes it by opening a page, and gets the waking page. | Gets the asleep page. Only a click on Wake it wakes it. |
| Scripts, API clients, gRPC and database drivers | Held until a pod is Ready, for up to wake.maxWait. After that, it’s closed, and the wake goes on. | Turned away. Over HTTP, a 503 that says how to wake it. Nothing wakes. |
| Crawlers and scanners | Wake it, unless they ask for a hidden file. | A 503. Nothing wakes. |
| Known probes, scrapers and uptime monitors | A 503 at both doors. Nothing wakes. | |
Tooling, such as a pull request bot, a portal or a chat command, by an annotation or kubectl napwake wake | Wakes it, at both doors. A condition on the workload tells the tool the wake was taken up. | |
| CI, through an activity annotation, or a dependent waking | Wakes it, at both doors. | |
| The forecast, if you turn it on | Wakes it 15 minutes before an hour it expects busy, at both doors. Where it asks first, the forecast learns those hours from people’s clicks and tooling’s wakes. | |
Wake on request in the docs covers readiness probes, timeouts behind an Ingress, and the Services it can’t route.
3 The forecast
It wakes the team’s rooms 15 minutes early.
Napwake learns each workload’s daily and weekly pattern with a Holt-Winters double seasonal model, fed every hour from its CPU and from the requests that wake it. With autoResume on, it wakes a paused workload 15 minutes before an hour it expects to be busy, so the first person in doesn’t wait.
- 108:45 autoResume wakes it, 15 minutes before an hour the forecast expects to be busy. Nobody waits.
- 209:10 The first person opens it, and finds it awake.
- 318:30 An hour after the last use at 17:30, idleAfter runs out and it’s paused.
autoResume on and the default idleAfter of one hour.- It learns before it acts. It learns its first week without scoring it. On office hours it acts from the Tuesday of the second week. With a few weeks of history in Prometheus, it acts from the first day.
- Weekends stay asleep, once the first weekend teaches it they’re quiet.
- The activity clock pauses workloads, and the forecast never does. A confident forecast holds off a pause before an hour it expects busy. It brings one forward only if you turn on
pauseEarly. - Check it first.
kubectl napwake forecastreplays it over your workloads’ own history before you trustautoResumewith them.
$ kubectl napwake forecast -n preview-42
staging: the forecast replayed over 28 days of CPU history from monitoring/prometheus-operated, times in UTC
NAMESPACE NAME FORECAST READY COLD WOKEN
preview-42 api acts from day one 9/10 1 08:45, 25m ahead
preview-42 worker (managed) acts from Mon 2 Nov 3/10 7 09:45, 20m ahead
preview-42 reports not within 4 weeks 0/6 6 -Example output from the plugin’s docs, columns trimmed. Forecasting in the docs has the model, the confidence it acts at, and the simulator that tests it.
4 What a pause changes
It never deletes anything.
Its one action is to pause, which scales a workload to zero replicas. The Deployment or StatefulSet stays, and so do its PVCs and their data. Only the pods are removed.
Stays
- The Deployment or StatefulSet
- Its Services, ClusterIP and DNS name
- Its PVCs and their data
- Its replica count, recorded to restore
Changes while it’s paused
- Replicas: zero
- Its Services route to the doorman
- A KEDA ScaledObject is held at zero
- The pods are gone
- It writes down what a pause will change before it scales anything, in its status and on the workload’s
napwake.dev/paused-fromannotation. So a pause cut short by a restart is finished or undone, and a resume goes back to the replicas it had. - Some things it won’t pause: a StatefulSet whose PVCs would be deleted on scale-down, Kubernetes’ own namespaces, and Napwake itself.
- It doesn’t fight you. Scaled up by anyone else, a paused workload counts as woken. Scaled to zero by hand, it’s left alone. If a GitOps tool undoes a pause, Napwake reports how to fix it.
Pause and resume in the docs has each step of a pause.
See it on your own cluster.
kubectl napwake scan shows what’s idle and what pausing would free, with nothing installed.