kat
provides a terminal UI for rendering, validating, and displaying local Kubernetes manifests. It eliminates the frustrating cycle of manually running commands, scrolling through endless output, and constantly losing context when working with Helm charts, Kustomize overlays, and other manifest generators.
β€οΈ Made with bubble tea, glow, and chroma.
- ποΈ Manifest browsing - Navigate hundreds of rendered manifests with fuzzy search and filtering, no more endless scrolling through terminal output
- β‘οΈ Live reload - Use
--watch
to automatically re-render when you modify source files, without losing your current context - π Error handling - Rendering and validation errors are displayed as overlays and disappear if reloading resolves the error
- π― Project detection - Automatically detect Helm charts, Kustomize projects, and custom manifest generators using powerful CEL expressions
- π§ͺ Tool integration - Define profiles for any manifest generator (Helm, Kustomize, CUE, KCL, Jsonnet, etc.) with pre/post-render hooks
- π Plugin system - Create custom keybind-triggered commands for common tasks that can't run on hooks, like dry-runs or diffs
- β
Custom validation - Run tools like
kubeconform
,kyverno
, or custom validators automatically on rendered output - π¨ Beautiful UI - Syntax-highlighted YAML with customizable themes and keybindings that match your preferences
brew install macropower/tap/kat
go install github.com/macropower/kat/cmd/kat@latest
Docker images are published to ghcr.io/macropower.
All images are configured with WORKDIR=/data
, so you can mount your current directory there to run kat
against your local files.
Run the latest alpine image:
docker run -it -v .:/data -e TERM=$TERM ghcr.io/macropower/kat:latest-alpine
Run the latest debian image:
docker run -it -v .:/data -e TERM=$TERM ghcr.io/macropower/kat:latest-debian
The default config is located at /config/kat/config.yaml
, and you can override it by mounting your own configuration file at that path.
There is also a scratch image that contains only the kat
binary, which is useful when you want to build your own image (which I generally recommend doing):
FROM alpine:latest
COPY --from=ghcr.io/macropower/kat:latest /kat /usr/local/bin/kat
# Add whatever customization you need here.
ENTRYPOINT ["/usr/local/bin/kat"]
You can install kat
using my NUR.
With nix-env
:
nix-env -iA kat -f https://github.com/macropower/nur-packages/archive/main.tar.gz
With nix-shell
:
nix-shell -A kat https://github.com/macropower/nur-packages/archive/main.tar.gz
With your flake.nix
:
{
inputs = {
macropower.url = "github:macropower/nur-packages";
};
# Reference the package as `inputs.macropower.packages.<system>.kat`
}
With devbox
:
devbox add github:macropower/nur-packages#kat
gh release download -R macropower/kat -p "kat_$(uname -s)_$(uname -m).tar.gz" -O - | tar -xz
And then move kat
to a directory in your PATH
.
curl -s https://api.github.com/repos/macropower/kat/releases/latest | \
jq -r ".assets[] |
select(.name | test(\"kat_$(uname -s)_$(uname -m).tar.gz\")) |
.browser_download_url" | \
xargs curl -L | tar -xz
And then move kat
to a directory in your PATH
.
You can download binaries from releases.
You can verify the authenticity and integrity of kat
releases.
See verification for more details.
Show help:
kat --help
Render a project in the current directory:
kat
Render a project and enable watch (live reloading):
kat -w
Render a project in a specific directory:
kat ./example/helm
Render a project in a specific directory using the ks
profile:
kat ./example/kustomize ks
Render a project and override the profile arguments:
kat ./example/kustomize ks -- build . --enable-helm
Render a project with command passthrough:
kat ./example/helm task -- helm:render
Render using data from stdin:
cat ./example/kustomize/resources.yaml | kat -f -
You can use kat --write-config
to generate a default configuration file at ~/.config/kat/config.yaml
. This file allows you to customize the behavior of kat
, such as the UI style, keybindings, rules for project detection, and profiles for rendering different types of projects.
Alternatively, you can find the default configuration file in pkg/config/config.yaml.
You can customize how kat
detects and renders different types of projects using rules and profiles in the configuration file. This system uses CEL (Common Expression Language) expressions to provide flexible file matching and processing.
Rules determine which profile should be used. Each rule contains:
match
(required): A CEL expression that returnstrue
if the rule should be appliedprofile
(required): The name of the profile to use when this rule matches
Rules use boolean CEL expressions with access to:
files
(list): All file paths in the directorydir
(string): The directory path being processed
rules:
- # Select the Helm profile if any Helm chart files exist
match: >-
files.exists(f, pathBase(f) in ["Chart.yaml", "Chart.yml"])
profile: helm
- # Select the Kustomize profile if any Kustomization files exist
match: >-
files.exists(f, pathBase(f) in ["kustomization.yaml", "kustomization.yml"])
profile: ks
- # Fallback: select the YAML profile if any YAML files exist
match: >-
files.exists(f, pathExt(f) in [".yaml", ".yml"])
profile: yaml
Profiles define how to render projects. They can be automatically selected by rules, or manually specified when kat
is invoked. Each profile contains:
command
(required): The command to executeargs
: Arguments to pass to the commandenv
: List of environment variables for the commandenvFrom
: List of sources for environment variablessource
: Define which files to watch for changes (when watch is enabled)ui
: UI configuration overrideshooks
: Initialization and rendering hooksinit
hooks are executed once whenkat
is initializedpreRender
hooks are executed before the profile's command is runpostRender
hooks are executed after the profile's command has run, and are provided the rendered output via stdin
plugins
: Custom commands that can be executed on-demand with keybindsdescription
(required): Human-readable description of what the plugin doeskeys
(required): Array of key bindings that trigger the plugincommand
(required): The command to executeargs
: Arguments to pass to the command
Profile source
expressions use list-returning CEL expressions with the same variables as rules.
profiles:
helm:
command: helm
args: [template, ., --generate-name]
source: >-
files.filter(f, pathExt(f) in [".yaml", ".yml", ".tpl"])
envFrom:
- callerRef:
pattern: "^HELM_.+"
ui:
theme: dracula
hooks:
init:
- command: helm
args: [version, --short]
preRender:
- command: helm
args: [dependency, build]
envFrom:
- callerRef:
pattern: "^HELM_.+"
postRender:
# Pass the rendered manifests via stdin to `kubeconform`.
- command: kubeconform
args: [-strict, -summary]
plugins:
dry-run:
command: helm
args: [install, ., -g, --dry-run]
envFrom:
- callerRef:
pattern: "^HELM_.+"
description: invoke helm dry-run
keys:
- code: ctrl+r
alias: βr
ks:
command: kustomize
args: [build, .]
source: >-
files.filter(f, pathExt(f) in [".yaml", ".yml"])
env:
- name: KUSTOMIZE_ENABLE_ALPHA_COMMANDS
value: "true"
ui:
compact: true
theme: tokyonight-storm
hooks:
init:
- command: kustomize
args: [version]
kat
provides custom CEL functions for file path operations:
pathBase(string)
: Returns the filename (e.g.,"Chart.yaml"
)pathExt(string)
: Returns the file extension (e.g.,".yaml"
)pathDir(string)
: Returns the directory pathyamlPath(file, path)
: Reads a YAML file and extracts a value using a YAML path expression
You can combine these with CEL's built-in functions like exists()
, filter()
, in
, contains()
, matches()
, and logical operators.
Example:
rules:
- match: >-
files.exists(f,
pathBase(f) == "Chart.yaml" &&
yamlPath(f, "$.apiVersion") == "v2")
profile: helm
profiles:
helm:
command: helm
args: [template, ., --generate-name]
source: >-
files.filter(f,
pathExt(f) in [".yaml", ".yml", ".tpl"])
For more details on CEL expressions and examples, see the CEL documentation.
The kat
configuration supports YAML anchor nodes, alias nodes, and merge keys. You can define common settings once and reuse them across the configuration.
profiles:
ks: &ks
command: kustomize
args: [build, .]
source: >-
files.filter(f, pathExt(f) in [".yaml", ".yml"])
hooks:
postRender:
- &kubeconform
command: kubeconform
args: [-strict, -summary]
ks-helm:
<<: *ks
args: [build, ., --enable-helm]
helm:
command: helm
args: [template, ., --generate-name]
source: >-
files.filter(f, pathExt(f) in [".yaml", ".yml", ".tpl"])
envFrom:
- callerRef:
pattern: "^HELM_.+"
hooks:
postRender:
- *kubeconform
β€οΈ Thanks to goccy/go-yaml.
Default config - By default, kat
includes a configuration that supports helm
, kustomize
, and generic YAML files. This is a great starting point for writing your own custom config:
- See
pkg/config/config.yaml
for the default configuration.
Support for custom tools - You can add support for other languages/tools like kcl
, jsonnet
, flux-local
, cue
, and so on:
rules:
- match: >-
files.exists(f, pathExt(f) == ".k")
profile: kcl
profiles:
kcl:
command: kcl
args: [run, .]
source: >-
files.filter(f, pathExt(f) == ".k")
envFrom:
- callerRef:
pattern: "^KCL_.+"
Content-based detection - Match based on file content, not just names:
rules:
- # Match Helm v3 specifically
match: >-
files.exists(f,
pathBase(f) == "Chart.yaml" &&
yamlPath(f, "$.apiVersion") == "v2")
profile: helm-v3
- # Match Kubernetes native manifests with specific API versions
match: >-
files.exists(f,
pathExt(f) in [".yaml", ".yml"] &&
yamlPath(f, "$.apiVersion") in ["apps/v1", "v1"])
profile: yaml
Using Task - If you use task
, you can use your tasks in the kat
config:
rules:
- match: >-
files.exists(f, pathBase(f) in ["Taskfile.yml", "Taskfile.yaml"])
profile: task
profiles:
task:
command: task
args: [render]
source: >-
files.filter(f, pathExt(f) in [".yaml", ".yml"])
hooks:
postRender:
- command: task
args: [validate]
Note that you should write your
task
to:
- Output the rendered manifests to stdout, and anything else to stderr.
- Tolerate being called from any directory in the project.
- E.g., instead of
./folder
, use{{joinPath .ROOT_DIR "folder"}}
.- Not require any additional arguments to run.
- You can reference
{{.USER_WORKING_DIR}}
to obtain the path that the user invokedkat
from/with.- E.g.,
vars: { PATH: "{{.PATH | default .USER_WORKING_DIR}}" }
If you are concerned about safety (i.e. accidentally calling a task defined by someone else), you can consider not including a rule for
task
and only allowing it to be invoked manually via the CLI args, or you could write a more narrow match expression (e.g.f.contains("/my-org/")
).
Configure a theme with --ui-theme
, KAT_UI_THEME
, or via config:
ui:
theme: "dracula"
You can optionally set different themes for different profiles:
profiles:
helm:
ui:
theme: "dracula"
# ...
ks:
ui:
theme: "tokyonight-storm"
# ...
We use Chroma for theming, so you can use any styles from the Chroma Style Gallery.
You can also add your own themes in the config:
ui:
theme: "my-custom-theme"
themes:
my-custom-theme:
styles:
background: "#abb2bf bg:#282c34"
punctuation: "#abb2bf"
keyword: "#c678dd"
name: "bold #e06c75"
comment: "italic #8b949e"
commentSpecial: "bold italic #8b949e"
# ...
Chroma uses the same syntax as Pygments. Define ui.themes.[name].styles
as a map of Pygments Tokens to Styles. You can then reference any theme in ui.theme
(or by using the corresponding flag / env var).