Configuration¶
All configuration goes through Helm values. Create a values.yaml with your overrides and apply it:
Notifications¶
KubeSentry notifies you two ways:
- Instant — a new Critical alert (Falco
CRITICAL,EMERGENCY, orALERT) fires every channel you've configured, in parallel, within seconds. - Daily digest — a 24-hour rollup, email only.
High and Warning alerts land on the dashboard and in the digest but never page you. See how severity works for the full mapping, and Verify your installation for a Critical trigger you can use to test delivery.
Every channel is off by default and enabled independently. Delivery failures are logged and never block an alert from being stored.
Email¶
Email is delivered through Resend. Because KubeSentry is self-hosted, it sends through your own Resend account — there is no default sender. Enabling email requires two things you provide:
- A Resend API key (
resendApiKeyfor testing, orexistingSecretin production). - A
fromaddress on a domain you have verified in that same Resend account (resend.com/domains). Resend only accepts mail from domains you control, so no shared or default address will work for delivery to your team.
to is a list.
notifications:
email:
enabled: true
from: "alerts@yourdomain.com" # an address on a domain YOU verified in Resend
to:
- "you@your-team.com"
- "security@your-team.com"
resendApiKey: "re_xxxxxxxx" # testing only — prefer existingSecret in production
If enabled: true but from (or a credential) is blank, the install still succeeds — detection and the dashboard always run — but no email is sent. The collector logs a loud warning at startup saying exactly what's missing, so check the collector logs if alerts aren't arriving.
403 from Resend
A resend returned 403 error in the collector logs means the from domain isn't verified on your Resend account. Verify the domain at resend.com/domains and set from to an address on it — never a domain you don't control.
Keep secrets out of values.yaml
Don't commit real API keys. Instead of inlining resendApiKey, create a Kubernetes Secret with the key resend-api-key and reference it:
Slack / Discord / Teams¶
Each chat channel takes a webhook URL. Inline webhookUrl is fine for testing; in production reference an existingSecret instead (keys: slack-webhook-url, discord-webhook-url, teams-webhook-url).
notifications:
slack:
enabled: true
webhookUrl: "https://hooks.slack.com/services/XXX/YYY/ZZZ"
# or: existingSecret: "kubesentry-slack" # key: slack-webhook-url
discord:
enabled: false
webhookUrl: ""
teams:
enabled: false
webhookUrl: ""
Generic webhook¶
Any HTTP endpoint of your own — a SIEM, a ticketing system, an internal handler. Instead of a chat-platform card, it receives the full alert as raw KubeSentry JSON, including the remediation advice:
{
"type": "kubesentry.alert",
"version": "1.0",
"timestamp": "2026-07-27T13:55:11Z",
"cluster": "prod-us-east",
"alert": {
"id": 42,
"rule": "KubeSentry: Sudoers or passwd modified in container",
"priority": "Critical",
"pod": "payments-api-7d8f",
"namespace": "prod",
"container": "payments-api",
"image": "ghcr.io/acme/payments-api",
"output": "13:55:11.144843182: Critical Sensitive auth file opened for writing …",
"count": 1,
"first_seen": "2026-07-27T13:55:11Z",
"last_seen": "2026-07-27T13:55:11Z",
"advice": {
"what": "A process opened /etc/passwd, /etc/shadow, or /etc/sudoers for writing …",
"triage": ["…"],
"remediation": ["…"],
"references": ["https://attack.mitre.org/techniques/T1136/"]
}
}
}
It's a POST with Content-Type: application/json and no authentication header —
if your endpoint needs a credential, put it in the URL (a signed path or query
token) or terminate the request behind a proxy that adds the header.
notifications:
generic:
enabled: true
webhookUrl: "https://my-system.example.com/kubesentry"
# or: existingSecret: "kubesentry-generic" # key: generic-webhook-url
Microsoft Teams: check which webhook URL you have
KubeSentry posts a MessageCard, the format Office 365 incoming webhook connectors accept. Microsoft is replacing those connectors with Power Automate Workflows, whose URLs expect a different payload — a MessageCard sent to a Workflows URL won't render as a card unless the flow is built to parse one. This is a connectivity question, not a capability one: on a classic connector URL, Teams gets the same alert content as every other channel, remediation included.
If your tenant has already retired classic connectors, point a generic webhook at a Workflow you author yourself. You get the complete alert as JSON and full control over the card format.
Wiring a channel from the CLI¶
If you'd rather not put credentials in a values file at all, create the Secret
yourself and point the chart at it. Reading the value with read -rs keeps it
out of your shell history and off the process list.
Slack:
read -rs SLACK_URL
kubectl create secret generic kubesentry-slack -n kubesentry \
--from-literal=slack-webhook-url="$SLACK_URL"
unset SLACK_URL
helm upgrade <release> kubesentry/kubesentry -n kubesentry --reuse-values \
--set notifications.slack.enabled=true \
--set notifications.slack.existingSecret=kubesentry-slack
Discord and Teams are identical — swap the channel name and the Secret
key (discord-webhook-url, teams-webhook-url):
read -rs DISCORD_URL
kubectl create secret generic kubesentry-discord -n kubesentry \
--from-literal=discord-webhook-url="$DISCORD_URL"
unset DISCORD_URL
helm upgrade <release> kubesentry/kubesentry -n kubesentry --reuse-values \
--set notifications.discord.enabled=true \
--set notifications.discord.existingSecret=kubesentry-discord
Generic webhook — same shape, key generic-webhook-url:
read -rs GENERIC_URL
kubectl create secret generic kubesentry-generic -n kubesentry \
--from-literal=generic-webhook-url="$GENERIC_URL"
unset GENERIC_URL
helm upgrade <release> kubesentry/kubesentry -n kubesentry --reuse-values \
--set notifications.generic.enabled=true \
--set notifications.generic.existingSecret=kubesentry-generic
Email needs the API key in a Secret plus two plain values — the verified sender and the recipient list:
read -rs RESEND_KEY
kubectl create secret generic kubesentry-email -n kubesentry \
--from-literal=resend-api-key="$RESEND_KEY"
unset RESEND_KEY
helm upgrade <release> kubesentry/kubesentry -n kubesentry --reuse-values \
--set notifications.email.enabled=true \
--set notifications.email.existingSecret=kubesentry-email \
--set notifications.email.from=alerts@yourdomain.com \
--set 'notifications.email.to={you@yourdomain.com,security@yourdomain.com}'
to is a list
Note the braces: --set 'notifications.email.to={a@x.com,b@x.com}' is how
Helm sets an array on the command line. Quote the whole argument so your
shell doesn't try to expand the braces.
<release> is whatever you named the Helm release — any name works, the
collector's Service name is pinned. --reuse-values keeps the rest of your
configuration as it is; without it, unspecified values fall back to chart
defaults.
Daily digest¶
A once-a-day summary of the last 24 hours, with a comparison against the previous day. Enabled by default at 09:00 UTC.
The digest is email only — it uses the email channel configured above and is never sent to Slack, Discord, or Teams. If email isn't configured, no digest goes out regardless of this setting.
Storage¶
Alert history is written to a PersistentVolume so it survives pod restarts and reinstalls.
The collector stores events in an embedded SQLite database on this volume. For most small teams the default size is plenty; increase it if you retain a lot of history.
Dashboard exposure¶
By default the dashboard Service is ClusterIP — reachable only in-cluster. The dashboard has no built-in authentication, so never expose it directly to the internet.
To make it reachable, front it with your own ingress or reverse proxy with authentication (e.g. an OAuth proxy or basic-auth annotations on your ingress controller), pointing at the kubesentry-collector Service on port 8080. So links in emails and webhooks use the right address, set the public URL:
Resource requests¶
The collector is lightweight (the image is a ~15 MB distroless build). Falco is the heavier component since it runs on every node. Defaults are sensible for small clusters; tune if needed:
Cluster identity¶
Name shown in the dashboard top bar, email subjects, and webhook payloads:
Tuning detection noise¶
Every cluster has legitimate activity that looks suspicious to default rules (init scripts, debugging, CI jobs). Falco rules are customizable — add exceptions or custom rules through the falco values block in the chart. See the Falco rules documentation for rule syntax.
Applying changes¶
Any config change is applied the same way:
Pods roll automatically. Alert history on the PVC is preserved across upgrades.