Introduction
nyl (pronounced like “nil”) is a Kubernetes manifest generator with Helm integration, designed to simplify the management of Kubernetes resources through a powerful templating system.
Goals
nyl aims to provide:
- High performance manifest generation
- Lightweight binary for easy distribution
- Clean, maintainable architecture for future development
- Powerful templating with Helm integration
Features
- Configuration loading (YAML, JSON)
- File discovery with upward directory traversal
nyl validatecommand with strict modenyl new projectcommand for project scaffoldingnyl new componentcommand for component scaffolding- Helm integration and component discovery
- Template rendering with Jinja2
- Kubernetes operations (diff, apply)
- Release state management with revision tracking
Key Features
- Safety: Memory safety without garbage collection
- Performance: Compiled binaries with minimal runtime overhead
- Concurrency: Efficient concurrent operations
- Reliability: Strong type system catches errors at compile time
- Rich ecosystem: Extensive library support
Architecture
nyl is structured into several key modules:
config: Project configuration loading and validationcli: Command-line interface and argument parsingtemplate: Jinja2 template rendering (Phase 3)kubernetes: Kubernetes client integration (Phase 4)resources: Resource definitions and transformationsgenerator: Manifest generation pipeline (Phase 3+)
Getting Started
This guide will help you get started with nyl-rs.
Installation
From Source
cd nyl-rs
cargo build --release
The binary will be available at target/release/nyl.
Install Locally
cargo install --path .
This installs nyl to ~/.cargo/bin/nyl.
Quick Start
1. Create a New Project
nyl new project my-app
cd my-app
This creates:
nyl.toml- Project configurationcomponents/- Directory for components
2. Add a Component
nyl new component v1.example.io MyApp
This creates a new component at components/v1.example.io/MyApp/ with:
Chart.yaml- Helm chart metadatavalues.yaml- Default valuesvalues.schema.json- JSON schema for validationtemplates/deployment.yaml- Kubernetes deployment template
3. Validate Your Project
nyl validate
Output:
✓ Found project config: /path/to/my-app/nyl.toml
✓ Components search path exists: /path/to/my-app/components
✓ Helm chart search path exists: /path/to/my-app
✓ Validation passed
4. Strict Validation
For CI/CD pipelines, use strict mode to treat warnings as errors:
nyl validate --strict
Project Structure
my-app/
├── nyl.toml # Project configuration
├── components/ # Component definitions
│ └── v1.example.io/
│ └── MyApp/
│ ├── Chart.yaml
│ ├── values.yaml
│ ├── values.schema.json
│ └── templates/
│ └── deployment.yaml
└── charts/ # Optional: additional Helm chart search path
Next Steps
- Read about Configuration
- Read the Component System
- Learn about the
newcommand - Learn about the
validatecommand
Configuration
nyl project settings are loaded from a single file: nyl.toml.
Configuration File Discovery
nyl searches for nyl.toml starting in the current directory and walking up parent directories.
Configuration Structure
nyl.toml supports:
[project]for project settings[profile.values.<name>]for profile values used in templates
[project]
components_search_paths = ["components"]
helm_chart_search_paths = ["."]
[profile.values.default]
namespace = "default"
replicas = 1
[profile.values.dev]
namespace = "dev"
replicas = 2
[project.aliases]
"myapi.io/v1/MyKind" = "oci://mycharts.org/my-kind@1.0.0"
Settings
project.components_search_paths
- Type: array of path strings
- Default:
["components"] - Meaning: Direct roots for component charts. Each root is scanned as:
<root>/<apiVersion>/<kind>/Chart.yaml
project.helm_chart_search_paths
- Type: array of path strings
- Default:
["."] - Meaning: Search paths used for Helm chart name resolution.
project.aliases
- Type: table/map of string to string
- Default: empty table
- Key format:
<apiVersion>/<kind> - Value format: same component shortcut format accepted in
kind(<repository>[#<name>][@<version>]) or a local component path - Meaning: Treat matching resources as component-style resources and resolve them directly to the configured target instead of
components_search_paths.
profile.values.<name>
- Type: object/map
- Default: none
- Meaning: Template values for profile
<name>exposed asvalues.*during rendering. - Selection:
--profile <name>selects a profile. If omitted, Nyl usesdefaultwhen available.
Example:
[profile.values.dev]
my_value = "Hello!"
replicas = 1
[profile.values.prod]
my_value = "World!"
replicas = 3
Template usage:
{{ values.my_value }}
{% if profile == "dev" %}
# dev-specific logic
{% endif %}
Example:
[project]
components_search_paths = ["components"]
[project.aliases]
"myapi.io/v1/MyKind" = "oci://registry-1.docker.io/bitnamicharts/nginx@18.2.4"
"platform.example.io/v1/IngressStack" = "https://charts.bitnami.com/bitnami#nginx@18.2.4"
Then this manifest is resolved through the alias target:
apiVersion: myapi.io/v1
kind: MyKind
metadata:
name: my-nginx
spec:
replicaCount: 2
Path Resolution
Relative paths are resolved against the directory that contains nyl.toml.
Example (/home/user/my-app/nyl.toml):
[project]
components_search_paths = ["components", "/opt/shared-components"]
helm_chart_search_paths = [".", "charts"]
Resolves to:
components_search_paths:/home/user/my-app/components/opt/shared-components
helm_chart_search_paths:/home/user/my-app/home/user/my-app/charts
Validation
Use:
nyl validate
Checks:
nyl.tomldiscovery and parse validity- existence of configured
components_search_paths - existence of configured
helm_chart_search_paths
Use strict mode in CI:
nyl validate --strict
JSON Schema
Generate schema from the current binary:
nyl generate schema config
Published schema artifact:
Component System
Nyl’s component system lets you map Kubernetes-style resources to Helm charts with a predictable lookup model.
For most teams, the default workflow is:
- Define local component charts under
components/<apiVersion>/<kind>/. - Reference those components from manifests with:
apiVersion: components.nyl.niklasrosenstein.github.com/v1kind: <apiVersion>/<kind>
- Put Helm values into
spec.
What a Component Resource Does
A Component resource is a compact wrapper around Helm rendering:
kindidentifies which chart to render.metadata.nameis used as Helm release name.metadata.namespaceis used as Helm release namespace (defaults todefaultif omitted).specis forwarded as Helm values.
Example:
apiVersion: components.nyl.niklasrosenstein.github.com/v1
kind: example/v1/Nginx
metadata:
name: web
namespace: default
spec:
replicaCount: 2
Choosing Between Component, HelmChart, and Aliases
- Use local Component resources for reusable, repo-owned building blocks.
- Use
HelmChartfor explicit chart definitions in platform-level manifests. - Use
project.aliaseswhen you want stable domainapiVersion/kindnames decoupled from chart source.
See:
- Authoring Local Components
- Resolution & Lookup Rules
- Remote Shortcuts & Aliases
- Component resource reference
Authoring Local Components
Local components are standard Helm charts placed under configured component search roots.
Directory Contract
Each component must exist at:
<components_search_path>/<apiVersion>/<kind>/
Chart.yaml
values.yaml
templates/
With default configuration:
[project]
components_search_paths = ["components"]
A component example/v1/Nginx resolves to:
components/example/v1/Nginx/Chart.yaml
Create a New Component
nyl new component example/v1 Nginx
This scaffolds:
components/example/v1/Nginx/
Chart.yaml
values.yaml
values.schema.json
templates/deployment.yaml
Consume the Component in a Manifest
apiVersion: components.nyl.niklasrosenstein.github.com/v1
kind: example/v1/Nginx
metadata:
name: web
namespace: default
spec:
replicaCount: 3
image:
repository: nginx
spec is passed to Helm as values, merged on top of chart defaults from values.yaml.
Operational Notes
- Keep component manifests separate from chart templates.
nyl renderaccepts a file path, not a directory. - Use
nyl validate --strictin CI to catch missing search paths early. - Prefer stable
apiVersion/kindnaming for internal component APIs.
Resolution & Lookup Rules
This page describes how Nyl resolves component resources into charts.
Local Lookup
For Component resources with local kinds, Nyl checks component search roots in order:
- Resolve
project.components_search_pathsfromnyl.toml. - Build candidate path
<root>/<kind>/Chart.yaml. - Use the first existing match.
For kind: example/v1/Nginx, Nyl looks for:
<root>/example/v1/Nginx/Chart.yaml
Relative search paths are resolved relative to the directory containing nyl.toml.
Search Path Precedence
components_search_paths = ["components", "/opt/shared-components"] means:
- Prefer repo-local components under
components/. - Fall back to
/opt/shared-components.
The first matching component wins.
Alias Resolution
Before local lookup, Nyl checks project.aliases for a <apiVersion>/<kind> match.
If an alias exists, the alias target is resolved directly and local lookup is skipped for that resource.
Example:
[project.aliases]
"platform.example.io/v1/IngressStack" = "oci://registry-1.docker.io/bitnamicharts/nginx@18.2.4"
Expansion and Filtering Caveat
-c/--component filters top-level input resources before expansion.
Example:
-c ConfigMapdoes not include ConfigMaps generated by Component or HelmChart expansion.- Use
-c HelmChartor match the component resource in input when you need those expansions.
Remote Shortcuts & Aliases
Local components should be the default for repo-owned APIs. Use remote shortcuts and aliases when you need indirection or external chart sources.
Shortcut Grammar
Component kind shortcuts use:
<base>[#<name>][@<version>]
base: repository URL or local pathname: chart name or Git subpathversion: chart version or Git ref
Remote bases are recognized by prefix:
http://orhttps://oci://git+
Remote Shortcut Examples
HTTP Helm repo:
apiVersion: components.nyl.niklasrosenstein.github.com/v1
kind: https://charts.bitnami.com/bitnami#nginx@18.2.4
metadata:
name: nginx-http
spec:
replicaCount: 2
OCI:
apiVersion: components.nyl.niklasrosenstein.github.com/v1
kind: oci://registry-1.docker.io/bitnamicharts/nginx@18.2.4
metadata:
name: nginx-oci
Git:
apiVersion: components.nyl.niklasrosenstein.github.com/v1
kind: git+https://github.com/prometheus-community/helm-charts#charts/prometheus@prometheus-25.28.0
metadata:
name: prometheus
namespace: monitoring
Git URL Parsing Edge Case
Parsing is right-to-left for separators to avoid misinterpreting @ in SSH-style URLs.
Example:
git+git@github.com:org/repo#charts/app@main
Interpreted as:
base = git+git@github.com:org/reponame = charts/appversion = main
Aliases for Stable APIs
Define stable domain resource types in nyl.toml:
[project.aliases]
"myapi.io/v1/MyKind" = "oci://registry-1.docker.io/bitnamicharts/nginx@18.2.4"
Then write manifests with standard apiVersion/kind:
apiVersion: myapi.io/v1
kind: MyKind
metadata:
name: my-kind-release
spec:
replicaCount: 2
When to Use What
Nyl supports three common patterns:
| Pattern | How you write it | Best for | Main benefit |
|---|---|---|---|
Full HelmChart resource | kind: HelmChart + spec.chart.* | Explicit platform manifests | Maximum clarity and explicit chart fields |
| Component shortcut | apiVersion: components... + kind: <shortcut> | Fast authoring near chart source | Minimal boilerplate |
project.aliases named kinds | apiVersion/kind mapped in nyl.toml | Domain APIs across teams | Stable semantic kinds decoupled from chart location |
- Local component path: internal platform APIs and reviewable chart source.
- Remote shortcut: quick direct dependency on an external chart source.
- Alias: stable contract for app teams while platform controls chart target centrally.
Troubleshooting Components
Component Not Found
Symptom:
- Render/diff/apply fails to resolve a component kind.
Checks:
- Confirm
apiVersionis exactlycomponents.nyl.niklasrosenstein.github.com/v1for Component resources. - Confirm
kindmatches<apiVersion>/<kind>directory layout. - Confirm
Chart.yamlexists at<components_search_path>/<kind>/Chart.yaml. - Confirm
components_search_pathsare valid from thenyl.tomllocation.
Example expected path for kind: example/v1/Nginx:
components/example/v1/Nginx/Chart.yaml
Wrong Chart Selected
Symptom:
- Nyl resolves a shared component when repo-local component was expected.
Cause:
- Search paths are ordered; first match wins.
Fix:
- Put preferred path earlier in
project.components_search_paths.
Alias Not Applied
Symptom:
- Resource is treated as a normal Kubernetes resource instead of a chart-backed resource.
Checks:
- Alias key is exact:
<apiVersion>/<kind>. - Manifest
apiVersionandkindexactly match key case and spelling. - Alias target uses a valid local path or remote shortcut syntax.
Filtering Doesn’t Show Generated Resources
Symptom:
-c ConfigMapmisses ConfigMaps produced by Helm rendering.
Cause:
- Filtering runs before expansion.
Fix:
- Filter top-level chart resources (
-c HelmChartor matching component resources), then inspect rendered output.
Fast Validation Loop
Use:
nyl validate --strict
nyl render <manifest-file>
This catches path/config issues before cluster-facing commands.
Git Integration
Nyl provides built-in Git repository management for fetching Helm charts and scanning application manifests from Git repositories. This enables declarative infrastructure management without requiring manual repository cloning.
Features
- Bare repositories: Minimal disk usage with shared object store
- Worktrees: Isolated checkouts for different refs (branches, tags, commits)
- Lazy fetching: Refs fetched first, objects downloaded on-demand
- Automatic caching: Repositories cached locally for fast subsequent access
- Concurrent access: Multiple refs from the same repository can be checked out simultaneously
Cache Directory
Nyl stores Git repositories in a cache directory to avoid redundant clones and improve performance.
Configuration
The cache directory is determined by:
NYL_CACHE_DIRenvironment variable (preferred).nyl/cache/in the current directory (fallback)
Example:
export NYL_CACHE_DIR=/var/cache/nyl
nyl render app.yaml
Cache Structure
$NYL_CACHE_DIR/git/
├── bare/
│ ├── {url_hash}-{repo_name}/ # Bare repository
│ │ ├── objects/
│ │ ├── refs/
│ │ └── ...
│ └── {url_hash2}-{repo_name2}/
└── worktrees/
├── {url_hash}-{ref_hash}/ # Worktree checkout
├── {url_hash}-{ref_hash2}/ # Another ref from same repo
└── {url_hash2}-{ref_hash}/ # Different repo
Key points:
- One bare repository per Git URL (shared object store)
- One worktree per unique URL + ref combination
- Worktrees share objects from bare repo (disk-efficient)
- URL and ref hashes ensure uniqueness and avoid conflicts
Using Git with HelmChart
HelmChart resources can reference Helm charts stored in Git repositories.
Basic Example
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: nginx
namespace: default
spec:
chart:
repository: git+https://github.com/bitnami/charts.git
version: main
name: bitnami/nginx
Parameters
repository: Git repository URL withgit+prefix (required)version: Branch, tag, or commit (optional, defaults to HEAD)name: Subdirectory within repository (optional)
Helm Dependencies
Nyl automatically detects and builds Helm chart dependencies for charts from Git repositories. When a chart contains a Chart.yaml with dependencies or a Chart.lock file, Nyl will:
- Detect the presence of dependencies
- Automatically run
helm dependency buildto fetch and build them - Continue with normal chart rendering
This means you can use charts with dependencies from Git repositories without any additional configuration:
# Chart.yaml in Git repository
apiVersion: v2
name: my-app
version: 1.0.0
dependencies:
- name: common
version: "^1.0"
repository: "oci://registry-1.docker.io/bitnamicharts"
- name: postgresql
version: "~12.0"
repository: "https://charts.bitnami.com/bitnami"
The dependencies will be automatically resolved when you reference this chart:
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: my-app
spec:
chart:
repository: git+https://github.com/example/charts.git
version: main
name: charts/my-app # Chart with dependencies
release:
name: my-app
namespace: default
Supported Ref Types
You can reference different types of Git refs:
Branch:
spec:
chart:
repository: git+https://github.com/example/charts.git
version: main
Tag:
spec:
chart:
repository: git+https://github.com/example/charts.git
version: v2.1.0
Commit SHA:
spec:
chart:
repository: git+https://github.com/example/charts.git
version: abc123def456
HEAD (default):
spec:
chart:
repository: git+https://github.com/example/charts.git
# version defaults to HEAD
Using Git with ApplicationGenerator
ApplicationGenerator resources scan Git repositories for Nyl manifests and automatically generate ArgoCD Applications.
Example
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
metadata:
name: cluster-apps
namespace: argocd
spec:
destination:
server: https://kubernetes.default.svc
namespace: argocd
source:
repoURL: https://github.com/example/gitops-demo.git
targetRevision: main
path: apps
include:
- "*.yaml"
- "*.yml"
exclude:
- ".*"
- "_*"
project: default
When rendered, Nyl will:
- Resolve repository source location (override, local checkout reuse, ArgoCD checkout reuse, or cached Git worktree)
- Use the
mainrevision - Navigate to the
apps/directory - Scan for YAML files matching the include/exclude patterns
- Generate ArgoCD Application manifests for each NylRelease found
ApplicationGenerator Resolution Order
For ApplicationGenerator, Nyl resolves the source repository in this order:
NYL_APPGEN_REPO_PATH_OVERRIDE, if set- The current local Git checkout, if the current
PWDis inside a repository whose remote matchesspec.source.repoURL, andspec.source.targetRevisionisHEADor the current branch name - ArgoCD’s local checkout via
ARGOCD_APP_SOURCE_* - The normal Git cache/worktree flow
If one step does not match, Nyl falls through to the next step automatically.
Current Local Checkout Reuse
When you run Nyl from inside the same repository referenced by an ApplicationGenerator, Nyl can skip clone/worktree operations and reuse the current local checkout.
Reuse is enabled only when all of these conditions match:
- The current
PWDis inside a Git repository - At least one local remote matches
spec.source.repoURLafter normalization spec.source.targetRevisionisHEAD, or exactly matches the current checked-out branch name
Notes:
- Detached
HEADonly matchestargetRevision: HEAD - If the remote or revision does not match, Nyl falls back to ArgoCD checkout reuse or the normal Git cache/worktree flow
ArgoCD Local Checkout Reuse
When running in ArgoCD plugin context, Nyl can also reuse the local checkout that ArgoCD already prepared.
Reuse is enabled only when all of these conditions match exactly:
spec.source.repoURLequalsARGOCD_APP_SOURCE_REPO_URL(normalized URL comparison)spec.source.targetRevisionequalsARGOCD_APP_SOURCE_TARGET_REVISION(exact string match)ARGOCD_APP_SOURCE_PATHcan be resolved relative to the current working directory
If any condition does not match, Nyl falls back to the normal Git cache/worktree resolution.
Local Worktree Override (Testing)
For local testing, you can bypass Git clone/worktree resolution and force all ApplicationGenerator resources to read from a local repository root:
export NYL_APPGEN_REPO_PATH_OVERRIDE=/path/to/local/repo
nyl render apps.yaml
export NYL_APPGEN_REPO_PATH_OVERRIDE=@git
nyl render apps.yaml
Behavior:
- If
NYL_APPGEN_REPO_PATH_OVERRIDEis set to a local path, Nyl resolvesspec.source.path/spec.source.pathsselectors under that path. - If
NYL_APPGEN_REPO_PATH_OVERRIDE=@git, Nyl discovers the Git repository root from the currentPWDand resolves selectors under that root. - If unset, Nyl tries current local checkout reuse first, then ArgoCD checkout reuse, then normal Git clone/cache/worktree resolution from
spec.source.repoURLandspec.source.targetRevision. - If the override path is invalid, or
@gitis used outside a Git repository, rendering fails immediately.
Selector behavior:
source.pathscans the selected directory non-recursively by default.- Use glob selectors (for example
**/*.yaml) orsource.pathsfor multi-selector/recursive workflows. - Include/exclude patterns are matched against paths relative to the repository root.
Performance Characteristics
Initial Clone
- Lazy fetching: Only refs are fetched initially (~KB), not objects (~MB/GB)
- On-demand objects: Commit objects fetched only when needed
- Bandwidth efficient: Minimal initial download
Subsequent Access
- Cache hit: Near-instant if ref already checked out
- Ref update: Only fetch new refs if branch updated
- Object reuse: Worktrees share objects from bare repo
Disk Usage
- Bare repo: One copy of objects for all refs
- Worktrees: Only working directory files (no .git directory)
- Efficient: Much smaller than full clones for each ref
Example disk usage for a 100MB repository with 3 refs:
- Traditional approach: 3 × 100MB = 300MB
- Nyl approach: 100MB (bare) + 3 × ~10MB (worktrees) = ~130MB
Authentication
Nyl supports both public and private Git repositories through multiple authentication methods.
Automatic Credential Discovery
When running inside a Kubernetes cluster with access to ArgoCD secrets, Nyl automatically discovers repository credentials from ArgoCD repository secrets. No additional configuration required!
Example: Using a private Helm chart
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: private-app
namespace: default
spec:
chart:
repository: git+git@github.com:myorg/private-charts.git
version: main
name: charts/app
If an ArgoCD repository secret exists for github.com, Nyl will automatically use those credentials.
Authentication Methods
-
ArgoCD Repository Secrets (Recommended)
- Credentials automatically discovered from
argocdnamespace - Supports both SSH keys and HTTPS tokens
- Zero configuration required
- See Repository Secrets for details
- Credentials automatically discovered from
-
SSH Agent
- Fallback for SSH URLs when no secret found
- Works with local development workflows
- Requires SSH agent running with key loaded
-
Public Repositories
- No authentication needed
- Works out of the box
Supported Credential Types
SSH Key Authentication:
- Private key stored in ArgoCD secret
- Recommended for production use
- Better security than HTTPS tokens
HTTPS Token Authentication:
- Personal access tokens or passwords
- Useful for HTTPS-only repositories
- Stored in ArgoCD secret
Example: Creating Repository Secret
# SSH authentication
kubectl create secret generic github-private \
-n argocd \
--from-literal=url=git@github.com:myorg/charts.git \
--from-file=sshPrivateKey=$HOME/.ssh/id_rsa
kubectl label secret github-private \
-n argocd \
argocd.argoproj.io/secret-type=repository
# HTTPS authentication
kubectl create secret generic github-https \
-n argocd \
--from-literal=url=https://github.com/myorg/charts.git \
--from-literal=username=myuser \
--from-literal=password=ghp_token123
kubectl label secret github-https \
-n argocd \
argocd.argoproj.io/secret-type=repository
For complete documentation on authentication, see Repository Secrets.
Limitations
-
Shallow clones not supported: libgit2 (the underlying library) doesn’t support shallow clones. Full repository history is fetched.
-
Force checkout: When reusing worktrees, local changes are discarded. Worktrees are treated as read-only checkouts.
Troubleshooting
Cache directory permissions
Problem: Permission denied when creating cache directory
Solution: Set NYL_CACHE_DIR to a writable location:
export NYL_CACHE_DIR=$HOME/.cache/nyl
Large repository performance
Problem: Initial clone is slow for large repositories
Explanation: Nyl fetches the full repository (no shallow clone support)
Workaround: Use a specific tag/commit to avoid fetching all branches:
spec:
chart:
repository: git+https://github.com/large/repo.git
version: v1.2.3 # Specific tag, not a branch
Stale cache
Problem: Git repository not updating with latest changes
Solution: Clear the cache directory:
rm -rf $NYL_CACHE_DIR/git/
Or for a specific repository:
# Find the cached repo
ls $NYL_CACHE_DIR/git/bare/
# Remove it
rm -rf $NYL_CACHE_DIR/git/bare/{hash}-{repo-name}
rm -rf $NYL_CACHE_DIR/git/worktrees/{hash}-*
Authentication failures
Problem: Cannot access private repository
Solution: See Repository Secrets for authentication setup
Examples
Multi-environment Chart from Git
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: myapp
namespace: production
spec:
chart:
repository: git+https://github.com/company/charts.git
version: stable
name: applications/myapp
values:
environment: production
replicas: 5
Development Branch
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: myapp
namespace: development
spec:
chart:
repository: git+https://github.com/company/charts.git
version: develop
name: applications/myapp
values:
environment: development
replicas: 1
Specific Version
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: myapp
namespace: staging
spec:
chart:
repository: git+https://github.com/company/charts.git
version: v2.1.0
name: applications/myapp
ApplicationGenerator with Filtering
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
metadata:
name: monitoring-apps
namespace: argocd
spec:
destination:
server: https://kubernetes.default.svc
namespace: argocd
source:
repoURL: https://github.com/company/infrastructure.git
targetRevision: main
path: monitoring
include:
- "*.yaml"
- "*.yml"
exclude:
- ".*" # Hidden files
- "_*" # Files starting with underscore
- "test_*" # Test files
project: monitoring
labels:
team: platform
category: monitoring
Best Practices
-
Pin versions in production: Use tags or commit SHAs for production deployments:
version: v1.2.3 # Tag # or version: abc123 # Commit SHA -
Use branches for development: Use branch refs for development environments:
version: develop # Branch name -
Set cache directory: Configure
NYL_CACHE_DIRin CI/CD environments:export NYL_CACHE_DIR=/cache/nyl -
Monitor cache size: Periodically clean up old worktrees if disk space is limited:
find $NYL_CACHE_DIR/git/worktrees -mtime +30 -delete -
Use subpaths: Keep charts in subdirectories for better organization:
spec: chart: repository: git+https://github.com/company/charts.git name: charts/applications/myapp
Commands
nyl provides several commands for managing Kubernetes manifests:
Available Commands
Phase 1 (Current)
Phase 2+ (Coming Soon)
rendering-pipeline- Shared rendering pipeline used by render/diff/applyrender- Render Kubernetes manifestsdiff- Show diff between rendered manifests and cluster stateapply- Apply rendered manifests to the cluster
Global Options
--verbose / -v
Enable verbose logging for debugging.
nyl --verbose validate
nyl -v new project my-app
--color <COLOR>
Control when to use colored output. Accepts three values:
auto(default) - Automatically detect if colors should be used based on TTY detectionalways- Always use colors, even when output is redirected to a file or pipenever- Never use colors
This applies to both:
- Colored output from commands (diffs, status indicators, etc.)
- Log messages from
tracing(INFO, WARN, ERROR)
This flag is particularly useful when:
- Redirecting output to a file where ANSI color codes are not desired
- Working in environments where terminal color support is inconsistent
- Forcing colored output in CI/CD pipelines that support ANSI colors
# Disable colors when piping to a file
nyl --color never diff | tee output.txt
# Force colors in CI/CD
nyl --color always diff
# Auto-detect (default behavior)
nyl diff
--help / -h
Show help information for any command.
nyl --help
nyl new --help
nyl validate --help
--version / -V
Show the version of nyl.
nyl --version
Rendering Pipeline
nyl render, nyl diff, and nyl apply share the same manifest generation pipeline.
Shared Pipeline Steps
- Load project configuration and select the active profile.
- Load secrets and build the template context.
- Load the input manifest file and render Jinja templates.
- Apply
--only-source-kindfiltering on top-level input resources (before expansion). - Expand resources recursively (
HelmChart,Component,RemoteManifest, aliases) with--max-depth. - Process
ApplicationGeneratorresources into Argo CDApplicationmanifests. - Apply Kyverno policies (Global scope currently supported).
- Deduplicate final manifests (last occurrence wins).
- Apply post-render kind filtering with
--only-kind/--exclude-kind.
Namespace Resolution (Online Mode)
In online mode, Nyl connects to Kubernetes and resolves missing metadata.namespace for namespaced resources.
Fallback order:
- Existing
metadata.namespace - Release namespace hint (
NylRelease.metadata.namespaceor--namespacefor release commands) - Kube context default namespace
- Error if no namespace can be determined
When --offline is used (render only), namespace resolution is skipped.
Online vs Offline
render --offline:- does not connect to the cluster for API discovery during rendering;
- requires
--kube-versionand--kube-api-versions; - skips namespace resolution.
render(without--offline),diff, andapply:- require cluster connectivity;
- initialize Kubernetes discovery once per command run and reuse it for scope checks.
Command-Specific Behavior After Rendering
render: outputs rendered YAML to stdout.diff: compares desired state with live cluster state and prints differences.apply: applies resources and manages release tracking/pruning.
Source Filter vs Post-Render Filters
--only-source-kindfilters only top-level resources in the input file, before expansion.--only-kind/--exclude-kindfilter the final rendered manifests, after expansion.
new
Create new nyl projects and components.
Synopsis
nyl new project <dir>
nyl new component <api-version> <kind>
nyl new project
Creates:
<dir>/
├── nyl.toml
└── components/
Generated nyl.toml:
[project]
components_search_paths = ["components"]
helm_chart_search_paths = ["."]
nyl new component
Creates component chart files under:
components/<api-version>/<kind>/
├── Chart.yaml
├── values.yaml
├── values.schema.json
└── templates/deployment.yaml
validate
Validate project configuration and search paths.
Synopsis
nyl validate [path] [--strict]
Checks
- Project config discovery (
nyl.toml) - TOML parse validity
- Existence of each
project.components_search_pathsentry - Existence of each
project.helm_chart_search_pathsentry
Examples
nyl validate
nyl validate --strict
nyl validate /path/to/project
render
Render Kubernetes manifests from nyl components and templates.
Synopsis
nyl render [OPTIONS] <FILE>
Description
The render command generates Kubernetes manifests and writes final YAML to stdout.
For detailed shared pipeline behavior (also used by diff and apply), see
Rendering Pipeline.
Arguments
<FILE>- Path to the manifest file to render (required)
Options
Common Options
--only-source-kind <KIND>- Filter top-level resources by kind (e.g.,ConfigMap,Deployment) or by apiVersion/kind (e.g.,apps/v1/Deployment) before expansion.--only-kind <KIND,...>- Filter final rendered manifests to only include specific kinds (post-render).--exclude-kind <KIND,...>- Filter final rendered manifests to exclude specific kinds (post-render, mutually exclusive with--only-kind).-p, --profile <PROFILE>- Profile to use for rendering. If omitted, Nyl triesdefault; if profiles exist butdefaultis missing, rendering fails with an error.--max-depth <MAX_DEPTH>- Maximum evaluation depth for recursive resource expansion (default: 10)--track-parent- Track parent resource information in annotations
Offline Mode Options
--offline- Skip Kubernetes discovery and use CLI-provided API information (useful for CI/CD)--kube-version <KUBE_VERSION>- Kubernetes version for Helm templating (required with –offline)--kube-api-versions <KUBE_API_VERSIONS>- Kubernetes API versions for Helm (required with –offline, comma-separated)
Examples
Basic Rendering
# Render a manifest file
nyl render manifest.yaml
# Render with specific profile
nyl render -p production manifest.yaml
# Filter top-level input resources
nyl render --only-source-kind ConfigMap manifest.yaml
# Filter by full apiVersion/kind
nyl render --only-source-kind apps/v1/Deployment manifest.yaml
# Filter final rendered output kinds
nyl render --only-kind Deployment,Service manifest.yaml
Offline Mode
# Render in offline mode (profile selection is unchanged)
nyl render --offline --kube-version 1.30 --kube-api-versions v1,apps/v1 manifest.yaml
Advanced Options
# Limit recursive expansion depth
nyl render --max-depth 5 manifest.yaml
# Track parent resources in annotations
nyl render --track-parent manifest.yaml
# Combine options
nyl render -p staging --max-depth 3 --track-parent manifest.yaml
Notes
- Nyl processes single files only. Directory paths are not supported.
- See Rendering Pipeline for namespace resolution, filter semantics, and online/offline behavior.
ApplicationGeneratorsource resolution first honorsNYL_APPGEN_REPO_PATH_OVERRIDE, then tries to reuse the current local Git checkout whenrepoURLmatches a local remote andtargetRevisionisHEADor the current branch, then falls back to ArgoCD checkout reuse and normal Git cache/worktree resolution.- Local ApplicationGenerator testing override: set
NYL_APPGEN_REPO_PATH_OVERRIDEto a local repository root (or@gitto auto-detect the Git root from the currentPWD) to make ApplicationGenerator scan the local filesystem instead of cloning. This affectsrender,diff, andapply(all use the same render pipeline). Using@gitoutside a Git repository fails with a configuration error. - ApplicationGenerator discovery semantics:
source.pathscans non-recursively by default; use glob selectors (orsource.paths) for recursive discovery, and include/exclude patterns match relative paths.
diff
Show the difference between rendered manifests and the current cluster state.
Synopsis
nyl diff [OPTIONS] <FILE>
Description
The diff command renders manifests, compares them with live cluster state, and prints changes.
For shared rendering behavior and namespace resolution details, see
Rendering Pipeline.
Arguments
<FILE>- Path to the manifest file to diff (required)
Options
Common Options
--only-source-kind <KIND>- Filter top-level resources by kind (e.g.,ConfigMap,Deployment) or by apiVersion/kind (e.g.,apps/v1/Deployment) before expansion.--only-kind <KIND,...>- Filter final rendered manifests to only include specific kinds (post-render).--exclude-kind <KIND,...>- Filter final rendered manifests to exclude specific kinds (post-render, mutually exclusive with--only-kind).-p, --profile <PROFILE>- Profile to use for rendering. If omitted, Nyl triesdefault; if profiles exist butdefaultis missing, diff fails with an error.--max-depth <MAX_DEPTH>- Maximum evaluation depth for recursive resource expansion (default: 10)--track-parent- Track parent resource information in annotations
Release Options
--name <NAME>- Release name (required if no NylRelease in file)--namespace <NAMESPACE>- Release namespace (required if no NylRelease in file)
Cluster Options
--context <CONTEXT>- Kubernetes context to use
Diff Options
--summary- Show summary only (counts, no detailed diff)--mode <MODE>- Diff mode:normalized(default) orrawnormalized: Uses server-side apply to filter server defaults (like kubectl diff)raw: Compares raw manifests without server normalization
--append-release- Preview diff as if current manifests were merged with the previous deployed release
Examples
Basic Diff
# Show diff for a manifest file
nyl diff manifest.yaml
# Diff with specific profile
nyl diff -p production manifest.yaml
# Diff only top-level ConfigMap resources
nyl diff --only-source-kind ConfigMap manifest.yaml
# Diff only final rendered Deployments
nyl diff --only-kind Deployment manifest.yaml
Summary Mode
# Show only the summary
nyl diff --summary manifest.yaml
Diff Modes
# Normalized mode (default) - filters server defaults
nyl diff --mode normalized manifest.yaml
# Raw mode - shows all differences including server defaults
nyl diff --mode raw manifest.yaml
Release Management
# Diff with explicit release name
nyl diff --name my-release --namespace default manifest.yaml
# Use different Kubernetes context
nyl diff --context production manifest.yaml
Output
The diff command shows:
- Green (+): Lines that will be added
- Red (-): Lines that will be removed
- Summary: Count of resources to create, update, or delete
Notes
- Nyl processes single files only. Directory paths are not supported.
- A
NylReleaseresource in the manifest provides release metadata automatically. - Normalized mode is recommended for most use cases as it matches kubectl diff behavior.
- If no previous release state exists, diff still compares desired resources against live state but cannot determine prune candidates; a warning is shown and
to deleteremains incomplete. - See Rendering Pipeline for namespace resolution and filter semantics.
apply
Apply rendered manifests to the Kubernetes cluster with release tracking.
Synopsis
nyl apply [OPTIONS] <FILE>
Description
The apply command renders manifests, applies them with server-side apply, and tracks release state.
For shared rendering behavior and namespace resolution details, see
Rendering Pipeline.
Arguments
<FILE>- Path to the manifest file to apply (required)
Options
Common Options
--only-source-kind <KIND>- Filter top-level resources by kind (e.g.,ConfigMap,Deployment) or by apiVersion/kind (e.g.,apps/v1/Deployment) before expansion.--only-kind <KIND,...>- Filter final rendered manifests to only include specific kinds (post-render).--exclude-kind <KIND,...>- Filter final rendered manifests to exclude specific kinds (post-render, mutually exclusive with--only-kind).-p, --profile <PROFILE>- Profile to use for rendering. If omitted, Nyl triesdefault; if profiles exist butdefaultis missing, apply fails with an error.--max-depth <MAX_DEPTH>- Maximum evaluation depth for recursive resource expansion (default: 10)--track-parent- Track parent resource information in annotations
Release Options
--name <NAME>- Release name (required if no NylRelease in file)--namespace <NAMESPACE>- Release namespace (required if no NylRelease in file)--append-release- Merge current resources with the previous deployed revision and skip pruning removed resources--no-release- Apply resources without creating release revisions, without release metadata, and without pruning
Cluster Options
--context <CONTEXT>- Kubernetes context to use
Examples
Basic Apply
# Apply a manifest file
nyl apply manifest.yaml
# Apply with specific profile
nyl apply -p production manifest.yaml
# Apply only top-level ConfigMap resources
nyl apply --only-source-kind ConfigMap manifest.yaml
# Apply only final rendered Deployments
nyl apply --only-kind Deployment manifest.yaml
Release Management
# Apply with explicit release name (overrides NylRelease if present)
nyl apply --name my-release --namespace default manifest.yaml
# Use different Kubernetes context
nyl apply --context production manifest.yaml
Dry Run
Use nyl diff to preview changes before running nyl apply.
No Release Mode
# Apply resources without release tracking or pruning
nyl apply --no-release manifest.yaml
Notes
- Nyl processes single files only. Directory paths are not supported.
- A
NylReleaseresource in the manifest provides release metadata automatically. - Release state is tracked in Kubernetes Secrets in the release namespace.
--no-releasedisables release tracking entirely. In this mode,nylcannot compute or prune resources removed from subsequent applies.- See Rendering Pipeline for namespace resolution and filter semantics.
generate
Generate auxiliary resources and configurations from Nyl manifests.
Usage
nyl generate <subcommand> [options]
Subcommands
argocd
Generate ArgoCD Application manifests from Nyl releases.
nyl generate argocd [OPTIONS] <PATH>
Arguments:
<PATH>: Directory to scan for Nyl release files
Options:
-o, --output <FILE>: Output file (default: stdout)--repo-url <URL>: Git repository URL for generated Applications--target-revision <REV>: Target revision (branch/tag/commit)--destination-server <URL>: Kubernetes server URL (default: https://kubernetes.default.svc)--destination-namespace <NS>: Namespace for Applications (default: argocd)--project <PROJECT>: ArgoCD project name (default: default)
Example:
# Generate Applications for all releases in clusters/default
nyl generate argocd clusters/default \
--repo-url https://github.com/myorg/gitops.git \
--target-revision main \
-o applications.yaml
schema config
Generate JSON Schema for nyl.toml to stdout.
nyl generate schema config
Relation to ApplicationGenerator
The nyl generate argocd command is a manual CLI tool for one-time generation of ArgoCD Applications from a directory of Nyl releases. It’s useful for:
- Initial ArgoCD bootstrap
- One-off Application generation
- CI/CD pipelines that don’t use ApplicationGenerator
The ApplicationGenerator resource is the recommended approach for ongoing management. It provides:
- Automatic discovery and generation during
nyl render - Integration with ArgoCD plugin for GitOps
- Declarative configuration
- Self-hosting bootstrap pattern
When to use each:
| Use Case | Tool | Why |
|---|---|---|
| Bootstrap ArgoCD | ApplicationGenerator | Declarative, self-hosting |
| Ongoing management | ApplicationGenerator | Automatic, GitOps-native |
| One-time bootstrap | nyl generate argocd | Manual control |
| CI/CD generation | nyl generate argocd | Explicit generation step |
Recommendation: Use ApplicationGenerator for most use cases. It’s processed during nyl render and integrates seamlessly with ArgoCD.
See Also
ArgoCD Integration
Nyl provides first-class support for ArgoCD through a custom configuration management plugin and the ApplicationGenerator resource. This integration enables GitOps-style deployments where ArgoCD can render Nyl manifests directly from Git repositories.
Key Features
- ArgoCD Plugin: Process Nyl manifests directly within ArgoCD
- ApplicationGenerator: Automatically generate ArgoCD Applications from Nyl releases
- Self-Hosting Pattern: Bootstrap ArgoCD with Nyl using a declarative approach
- Live Diffing: See differences between Git state and cluster state
- Automated Sync: Optionally enable automatic synchronization and pruning
How It Works
The Plugin
Nyl provides an ArgoCD configuration management plugin that acts as a bridge between ArgoCD and Nyl. When ArgoCD syncs an Application that uses the Nyl plugin:
- ArgoCD clones the Git repository
- The Nyl plugin is invoked to render manifests
- Nyl processes the YAML files (HelmCharts, NylRelease, etc.)
- Rendered Kubernetes manifests are returned to ArgoCD
- ArgoCD applies the manifests to the cluster
ApplicationGenerator
The ApplicationGenerator resource enables automatic discovery and generation of ArgoCD Applications. When you use nyl render on a file containing an ApplicationGenerator:
- Nyl scans the configured directory for YAML files
- Each file with a NylRelease is discovered
- An ArgoCD Application is generated for each NylRelease
- The ApplicationGenerator is replaced with the generated Applications
This pattern is particularly useful for:
- Managing multiple applications in a single repository
- Bootstrapping ArgoCD to manage itself
- Multi-cluster deployments
- Directory-based organization
Getting Started
- Install the ArgoCD Plugin in your ArgoCD installation
- Follow the Bootstrapping Guide to set up Nyl with ArgoCD
- Use ApplicationGenerator to manage applications at scale
- Review Best Practices for production deployments
Use Cases
Single Application: Use the Nyl plugin directly in ArgoCD Applications to render Nyl manifests from Git.
Multi-Application: Use ApplicationGenerator to scan a directory and automatically create Applications for each discovered release.
Bootstrap Pattern: Use ApplicationGenerator to have ArgoCD manage itself and all applications declaratively.
Helm Integration: Leverage Nyl’s HelmChart resource within ArgoCD to deploy Helm charts with full templating support.
Plugin Installation
The Nyl ArgoCD plugin enables ArgoCD to render Nyl manifests directly from Git repositories. This guide covers plugin installation and configuration.
Prerequisites
- ArgoCD 2.4+ installed in your cluster
- Access to modify ArgoCD’s configuration
- Nyl binary available in the ArgoCD repo-server
Installation Methods
Method 1: Custom ArgoCD Image (Recommended)
Build a custom ArgoCD repo-server image with Nyl included:
FROM quay.io/argoproj/argocd:v2.9.3
# Switch to root to install Nyl
USER root
# Download and install Nyl
RUN curl -L https://github.com/NiklasRosenstein/nyl/releases/download/vX.Y.Z/nyl-linux-amd64 \
-o /usr/local/bin/nyl && \
chmod +x /usr/local/bin/nyl
# Switch back to argocd user
USER argocd
Deploy this custom image by updating the argocd-repo-server deployment:
spec:
template:
spec:
containers:
- name: argocd-repo-server
image: your-registry/argocd-with-nyl:v2.9.3
Method 2: Init Container
Use an init container to download Nyl into a shared volume:
spec:
template:
spec:
initContainers:
- name: install-nyl
image: alpine:3.18
command:
- sh
- -c
- |
apk add --no-cache curl
curl -L https://github.com/NiklasRosenstein/nyl/releases/download/vX.Y.Z/nyl-linux-amd64 \
-o /plugins/nyl
chmod +x /plugins/nyl
volumeMounts:
- name: plugins
mountPath: /plugins
containers:
- name: argocd-repo-server
volumeMounts:
- name: plugins
mountPath: /usr/local/bin/nyl
subPath: nyl
volumes:
- name: plugins
emptyDir: {}
Plugin Configuration
Configure the Nyl plugin in the ArgoCD ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
configManagementPlugins: |
- name: nyl-v2
generate:
command: ["/bin/sh", "-c"]
args:
- |
TEMPLATE_INPUT="${ARGOCD_ENV_NYL_CMP_TEMPLATE_INPUT:-${NYL_CMP_TEMPLATE_INPUT:-}}"
test -n "$TEMPLATE_INPUT" || { echo "NYL_CMP_TEMPLATE_INPUT is required" >&2; exit 1; }
nyl render "$TEMPLATE_INPUT"
NYL_CMP_TEMPLATE_INPUT path semantics:
- Values that start with
/are treated as repository-root-relative (for example:/gitops/system/argocd.yaml). - Values without a leading
/are treated as relative tospec.source.path(recommended, usually just the file name).
Verification
Test the plugin installation:
- Create a test Application using the Nyl plugin:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: test-nyl
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/your-org/your-repo
targetRevision: HEAD
path: path/to/nyl/manifests
plugin:
name: nyl-v2
destination:
server: https://kubernetes.default.svc
namespace: default
- Check the Application status:
kubectl get application test-nyl -n argocd
argocd app get test-nyl
- Verify manifest rendering:
argocd app manifests test-nyl
Passing Environment Variables
You can pass environment variables to the Nyl plugin for configuration:
source:
plugin:
name: nyl-v2
env:
- name: NYL_CMP_TEMPLATE_INPUT
value: apps.yaml
- name: NYL_RELEASE_NAME
value: my-app
- name: NYL_RELEASE_NAMESPACE
value: production
Use apps.yaml (source-path-relative) in most cases. Use a leading / only when you intentionally need a repository-root-relative path.
Troubleshooting
Plugin Not Found
If ArgoCD reports “plugin not found”, check:
- The plugin name matches exactly:
nyl-v2 - The argocd-cm ConfigMap is in the
argocdnamespace - The repo-server pods have been restarted after configuration changes
kubectl rollout restart deployment argocd-repo-server -n argocd
Command Not Found
If the plugin fails with “nyl: command not found”:
- Verify Nyl binary is in the PATH:
/usr/local/bin/nyl - Check file permissions (should be executable)
- Test manually in the repo-server pod:
kubectl exec -it deployment/argocd-repo-server -n argocd -- nyl --version
Profile Not Found
If Nyl reports “Profile ‘default’ not found”:
- Ensure your repository defines
[profile.values.default]innyl.toml - Check the profile name matches what you’re referencing
- Verify
nyl.tomlis in the repository root or search path
Next Steps
Bootstrapping ArgoCD with Nyl
This guide demonstrates how to bootstrap ArgoCD using Nyl with a self-hosting pattern, where ArgoCD manages itself and all applications through the ApplicationGenerator resource.
Overview
The bootstrap pattern works as follows:
- Manually apply a bootstrap manifest to install ArgoCD
- The bootstrap includes an ArgoCD Application pointing to
apps.yaml - ArgoCD syncs the “apps” Application, running
nyl render apps.yaml - Nyl sees ApplicationGenerator in
apps.yamland scans for NylRelease files - Nyl generates ArgoCD Applications for each found NylRelease
- ArgoCD receives and creates all child Applications
- ArgoCD now manages all applications (including itself) declaratively
Prerequisites
kubectlconfigured with cluster access- Git repository for storing manifests
- Basic understanding of ArgoCD concepts
Directory Structure
Organize your repository as follows:
gitops-repo/
├── bootstrap.yaml # Initial bootstrap (apply once manually)
├── apps.yaml # ApplicationGenerator definition
├── argocd/ # ArgoCD installation manifests
│ └── argocd.yaml # HelmChart for ArgoCD
├── clusters/
│ └── default/ # Applications for default cluster
│ ├── app1.yaml # Application 1 with NylRelease
│ ├── app2.yaml # Application 2 with NylRelease
│ └── app3.yaml # Application 3 with NylRelease
├── nyl.toml # Nyl project + profile configuration
└── nyl-secrets.yaml # Secrets configuration
Step 1: Create Configuration Files
nyl.toml
[project]
components_search_paths = ["components"]
helm_chart_search_paths = ["."]
[profile.values.default]
# default profile values
nyl-secrets.yaml
type: null
Step 2: Create ArgoCD Installation Manifest
Create argocd/argocd.yaml:
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: NylRelease
metadata:
name: argocd
namespace: argocd
---
apiVersion: v1
kind: Namespace
metadata:
name: argocd
---
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: argocd
namespace: argocd
spec:
chart:
repository: https://argoproj.github.io/argo-helm
name: argo-cd
version: "5.51.6"
values:
# ArgoCD configuration
server:
extraArgs:
- --insecure # For testing; use TLS in production
# Configure the Nyl plugin
repoServer:
volumes:
- name: plugins
emptyDir: {}
initContainers:
- name: install-nyl
image: alpine:3.18
command: [sh, -c]
args:
- |
apk add --no-cache curl
curl -L https://github.com/NiklasRosenstein/nyl/releases/latest/download/nyl-linux-amd64 \
-o /plugins/nyl
chmod +x /plugins/nyl
volumeMounts:
- name: plugins
mountPath: /plugins
volumeMounts:
- name: plugins
mountPath: /usr/local/bin/nyl
subPath: nyl
# Plugin configuration
configs:
cm:
configManagementPlugins: |
- name: nyl-v2
generate:
command: ["/bin/sh", "-c"]
args:
- |
TEMPLATE_INPUT="${ARGOCD_ENV_NYL_CMP_TEMPLATE_INPUT:-${NYL_CMP_TEMPLATE_INPUT:-}}"
test -n "$TEMPLATE_INPUT" || { echo "NYL_CMP_TEMPLATE_INPUT is required" >&2; exit 1; }
nyl render "$TEMPLATE_INPUT"
NYL_CMP_TEMPLATE_INPUT should usually be source-path-relative (for example apps.yaml when spec.source.path points at that directory). A leading / marks a repository-root-relative path.
Step 3: Create ApplicationGenerator
Create apps.yaml with the ApplicationGenerator resource:
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
metadata:
name: cluster-apps
namespace: argocd
spec:
# Where to create generated Applications
destination:
server: https://kubernetes.default.svc
namespace: argocd
# Source repository configuration
source:
repoURL: https://github.com/your-org/gitops-repo.git
targetRevision: HEAD
path: clusters/default
# Optional: file filtering
include:
- "*.yaml"
- "*.yml"
exclude:
- ".*" # Skip hidden files
- "_*" # Skip files starting with underscore
- "apps.yaml" # Avoid recursion
# ArgoCD project for generated Applications
project: default
# Default sync policy for all generated Applications
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
# Labels added to all generated Applications
labels:
managed-by: nyl
generator: cluster-apps
# Annotations added to all generated Applications
annotations:
docs-url: https://wiki.example.com/cluster-apps
Step 4: Create Sample Applications
Create clusters/default/nginx.yaml:
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: NylRelease
metadata:
name: nginx
namespace: default
---
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: nginx
namespace: default
spec:
chart:
repository: https://charts.bitnami.com/bitnami
name: nginx
version: "15.4.4"
values:
replicaCount: 2
service:
type: ClusterIP
Create clusters/default/redis.yaml:
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: NylRelease
metadata:
name: redis
namespace: default
---
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: redis
namespace: default
spec:
chart:
repository: https://charts.bitnami.com/bitnami
name: redis
version: "18.4.0"
values:
architecture: standalone
auth:
enabled: false
Step 5: Create Bootstrap Manifest
Create bootstrap.yaml:
# Namespace for ArgoCD
apiVersion: v1
kind: Namespace
metadata:
name: argocd
---
# Application to install ArgoCD itself
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: argocd
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/your-org/gitops-repo.git
targetRevision: HEAD
path: argocd
plugin:
name: nyl-v2
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
---
# Application generator that creates all other Applications
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: apps
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/your-org/gitops-repo.git
targetRevision: HEAD
path: .
plugin:
name: nyl-v2
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
Important: In the apps Application, the path is . (repository root) because apps.yaml contains the ApplicationGenerator which will scan clusters/default.
Step 6: Bootstrap the Cluster
- Commit all files to your Git repository:
git add .
git commit -m "Initial Nyl + ArgoCD bootstrap"
git push origin main
- Apply the bootstrap manifest to your cluster:
kubectl apply -f bootstrap.yaml
- Wait for ArgoCD to install:
kubectl wait --for=condition=available --timeout=5m \
deployment/argocd-server -n argocd
- Access ArgoCD UI:
# Port forward to access UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Get admin password
kubectl get secret argocd-initial-admin-secret -n argocd \
-o jsonpath='{.data.password}' | base64 -d
- Verify Applications were created:
kubectl get applications -n argocd
Expected output:
NAME SYNC STATUS HEALTH STATUS
argocd Synced Healthy
apps Synced Healthy
nginx Synced Healthy
redis Synced Healthy
How It Works
When you apply bootstrap.yaml:
- ArgoCD Namespace Created: The
argocdnamespace is created - ArgoCD Application Syncs: ArgoCD installs itself via the Nyl plugin
- Apps Application Syncs: The “apps” Application runs
nyl render "$NYL_CMP_TEMPLATE_INPUT"(for exampleapps.yaml) - ApplicationGenerator Processes: Nyl finds
apps.yaml, sees ApplicationGenerator - Directory Scanned: Nyl scans
clusters/default/for YAML files - Applications Generated: For each NylRelease found (nginx, redis), Nyl generates an ArgoCD Application
- Applications Created: ArgoCD receives the generated Applications and creates them
- Child Applications Sync: Each generated Application syncs its resources to the cluster
Verification
Check Application Status
# View all applications
argocd app list
# Get details of a specific application
argocd app get nginx
# View application tree
argocd app get nginx --show-operation
# View rendered manifests
argocd app manifests nginx
Verify Nyl Plugin
# Check if Nyl is available in repo-server
kubectl exec -it deployment/argocd-repo-server -n argocd -- nyl --version
# View plugin configuration
kubectl get configmap argocd-cm -n argocd -o yaml | grep -A10 configManagementPlugins
View Generated Applications
# See what ApplicationGenerator produced
kubectl exec -it deployment/argocd-repo-server -n argocd -- \
sh -c 'cd /tmp && git clone https://github.com/your-org/gitops-repo.git && \
cd gitops-repo && nyl render apps.yaml'
Adding New Applications
To add a new application to be managed by ArgoCD:
- Create a new YAML file in
clusters/default/:
# clusters/default/postgres.yaml
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: NylRelease
metadata:
name: postgres
namespace: database
---
apiVersion: v1
kind: Namespace
metadata:
name: database
---
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: postgres
namespace: database
spec:
chart:
repository: https://charts.bitnami.com/bitnami
name: postgresql
version: "13.2.24"
values:
auth:
username: myuser
database: mydb
- Commit and push to Git:
git add clusters/default/postgres.yaml
git commit -m "Add PostgreSQL application"
git push origin main
- ArgoCD automatically detects the change and creates the
postgresApplication
No manual intervention needed! The ApplicationGenerator pattern discovers new applications automatically.
Troubleshooting
Applications Not Created
If generated Applications don’t appear:
-
Check the “apps” Application status:
argocd app get apps -
View the rendered manifests:
argocd app manifests apps -
Check if ApplicationGenerator is processing correctly:
# Should show ArgoCD Applications, not the ApplicationGenerator argocd app manifests apps | grep "kind: Application"
Plugin Failures
If the Nyl plugin fails:
-
Check repo-server logs:
kubectl logs deployment/argocd-repo-server -n argocd -f -
Verify Nyl installation:
kubectl exec deployment/argocd-repo-server -n argocd -- which nyl kubectl exec deployment/argocd-repo-server -n argocd -- nyl --version -
Test Nyl render manually:
kubectl exec -it deployment/argocd-repo-server -n argocd -- sh # Inside the pod: cd /tmp git clone <your-repo> cd <repo-name> nyl render apps.yaml
Sync Issues
If Applications fail to sync:
-
Check Application health:
argocd app get <app-name> -
View sync operation details:
argocd app get <app-name> --show-operation -
Force refresh and sync:
argocd app sync <app-name> --force
Next Steps
- ApplicationGenerator Reference - Detailed field documentation
- Best Practices - Production recommendations
- Multi-Cluster Setup - Managing multiple clusters
ApplicationGenerator Reference
The ApplicationGenerator resource enables automatic discovery and generation of ArgoCD Applications from NylRelease files in a Git repository directory.
Note: Repository sources are resolved automatically by Nyl. Depending on context, Nyl may reuse an existing local checkout or clone into its Git cache. See the Git Integration guide for cache management and resolution details.
Resource Definition
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
metadata:
name: string # Generator name (optional)
namespace: string # Namespace (optional, typically 'argocd')
spec:
destination: # Required
server: string # Kubernetes API server URL
namespace: string # Namespace for generated Applications
source: # Required
repoURL: string # Git repository URL
targetRevision: string # Branch, tag, or commit (default: "HEAD")
path: string # Single selector to scan (mutually exclusive with paths)
paths: [string] # Multiple selectors to scan (mutually exclusive with path)
include: [string] # Include patterns (default: ["*.yaml", "*.yml"])
exclude: [string] # Exclude patterns (default: [".*", "_*", ".nyl/**"])
project: string # ArgoCD project (default: "default")
syncPolicy: # Optional sync policy for generated Applications
automated:
prune: bool
selfHeal: bool
syncOptions: [string]
applicationNameTemplate: string # Naming template (default: "{{ .release.name }}")
labels: {string: string} # Labels for generated Applications
annotations: {string: string} # Annotations for generated Applications
releaseCustomization: # Optional NylRelease override policy
allowedPaths: [string]
deniedPaths: [string]
Field Reference
metadata
Standard Kubernetes metadata for the ApplicationGenerator resource itself.
- name (optional): Identifier for this generator
- namespace (optional): Namespace where this resource lives (typically
argocd)
spec.destination
Defines where generated Applications should be created and what cluster they target.
-
server (required): Kubernetes API server URL for the target cluster
- Example:
https://kubernetes.default.svc(in-cluster) - Example:
https://my-cluster.example.com:6443(external cluster)
- Example:
-
namespace (required): Namespace where generated Applications are created
- Typically
argocd - Must match where ArgoCD is installed
- Typically
spec.source
Configures the Git repository and directory scanning behavior.
-
repoURL (required): Git repository URL
- HTTPS:
https://github.com/org/repo.git - SSH:
git@github.com:org/repo.git
- HTTPS:
-
targetRevision (optional, default:
"HEAD"): Git reference to use- Branch:
main,develop - Tag:
v1.0.0 - Commit:
abc123def456
- Branch:
-
path (required if
pathsis not set): Single selector to scan- Relative to repository root
- Can be a file, directory, or glob selector
- Directory selectors are scanned non-recursively by default
-
paths (required if
pathis not set): Multiple selectors to scan- Relative to repository root
- Can include glob selectors (for example
clusters/*/appsor**/*.yaml) - Mutually exclusive with
path
-
include (optional, default:
["*.yaml", "*.yml"]): Glob patterns for files to include- Patterns without
/match basename - Patterns with
/match relative path from repository root - Multiple patterns are OR’d together
- Patterns without
-
exclude (optional, default:
[".*", "_*", ".nyl/**"]): Glob patterns for files to exclude- Takes precedence over include patterns
- Default excludes hidden files (
.), underscore-prefixed files (_), and Nyl cache directories - Example:
["test_*", ".*", "backup*"]
spec.project
- project (optional, default:
"default"): ArgoCD project name for generated Applications- Must be an existing ArgoCD AppProject
- Used for RBAC and resource restrictions
spec.syncPolicy
Optional default sync policy applied to all generated Applications.
-
automated (optional): Enable automated sync
- prune (bool): Delete resources no longer defined in Git
- selfHeal (bool): Force resource state to match Git
-
syncOptions (optional): List of sync options
CreateNamespace=true: Create destination namespace if missingPruneLast=true: Prune resources after other operationsRespectIgnoreDifferences=true: Respect ignore differencesApplyOutOfSyncOnly=true: Only apply resources that are out of sync
spec.applicationNameTemplate
- applicationNameTemplate (optional, default:
"{{ .release.name }}"): Template for Application names- Currently supports:
{{ .release.name }},{{ .release.namespace }} - Future: Full Handlebars/Tera template support
- Currently supports:
spec.labels
Key-value map of labels to add to all generated Applications.
Example:
labels:
managed-by: nyl
team: platform
environment: production
spec.annotations
Key-value map of annotations to add to all generated Applications.
Example:
annotations:
docs-url: https://wiki.example.com/apps
team-slack: "#platform-team"
spec.releaseCustomization
Controls project-level NylRelease.spec.argocd.applicationOverride customization of generated Applications.
NylReleaseoverrides are always evaluated against effectiveallowedPaths/deniedPaths.+<field>append overrides are matched against the canonical field path without the+prefix.- Example:
spec.syncPolicy.+syncOptionsis checked asspec.syncPolicy.syncOptions.
- Example:
- If
releaseCustomizationis omitted, defaults are used. allowedPathsanddeniedPathsuse dotted field globs:*matches one segment (does not cross dots)**matches multiple segments (crosses dots)
- If both allow and deny match, deny takes precedence.
- If
allowedPathsis omitted or null, defaults are used:metadata.annotations."pref.argocd.argoproj.io/*"spec.info.**spec.ignoreDifferences.**spec.syncPolicy.**
- If
allowedPathsis an empty list, no fields are allowed.
Ignored fields (unsupported/disallowed/invalid) are not applied and are reported in generated Application.spec.info.
File Filtering
The ApplicationGenerator scans the configured directory and applies include/exclude patterns:
Pattern Matching
Patterns use standard glob syntax:
*.yaml- Matches files ending with.yaml*.yml- Matches files ending with.ymlapp*- Matches files starting withapp.*- Matches hidden files (starting with.)_*- Matches files starting with underscoretest_*.yaml- Matchestest_*.yamlfiles**/*.yaml- Recursive match for YAML files- Exact match:
apps.yaml- Matches onlyapps.yaml
Filtering Logic
- Expand selectors from
path/pathsinto candidate files - File must match at least one
includepattern - File must NOT match any
excludepattern - Nyl parses the file and looks for a NylRelease resource
- If NylRelease found, an Application is generated
Default Patterns
By default:
- Include:
["*.yaml", "*.yml"]- All YAML files - Exclude:
[".*", "_*", ".nyl/**"]- Hidden files, underscore-prefixed files, and Nyl cache paths
This prevents accidental inclusion of:
- Hidden files like
.secrets.yaml,.git/ - Backup files like
_backup.yaml - Test files like
_test_app.yaml
Examples
Basic Usage
Simplest ApplicationGenerator for scanning a directory:
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
metadata:
name: my-apps
namespace: argocd
spec:
destination:
server: https://kubernetes.default.svc
namespace: argocd
source:
repoURL: https://github.com/myorg/gitops.git
path: apps
This scans apps/ for *.yaml and *.yml files, generates an Application for each NylRelease found.
With Automated Sync
Enable automatic synchronization and pruning:
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
metadata:
name: production-apps
namespace: argocd
spec:
destination:
server: https://kubernetes.default.svc
namespace: argocd
source:
repoURL: https://github.com/myorg/gitops.git
targetRevision: production
path: clusters/production
project: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- PruneLast=true
With Release-Level +syncOptions
Generator defaults can be extended from a discovered NylRelease instead of replaced.
ApplicationGenerator:
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
metadata:
name: home-lab
namespace: argocd
spec:
destination:
server: https://kubernetes.default.svc
namespace: argocd
source:
repoURL: git@gitlab.com:NiklasRosenstein/config.git
targetRevision: HEAD
path: kasoku.netbird.selfhosted/gitops/home-lab
project: home-lab
syncPolicy:
syncOptions:
- ServerSideApply=true
- ApplyOutOfSyncOnly=true
NylRelease:
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: NylRelease
metadata:
name: service-proxies
namespace: home-proxy
spec:
argocd:
applicationOverride:
spec:
syncPolicy:
+syncOptions:
- RespectIgnoreDifferences=false
Generated Application excerpt:
spec:
syncPolicy:
syncOptions:
- ServerSideApply=true
- ApplyOutOfSyncOnly=true
- RespectIgnoreDifferences=false
Custom File Filtering
Include only specific files and exclude test files:
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
metadata:
name: filtered-apps
namespace: argocd
spec:
destination:
server: https://kubernetes.default.svc
namespace: argocd
source:
repoURL: https://github.com/myorg/gitops.git
path: apps
include:
- "prod-*.yaml"
- "core-*.yaml"
exclude:
- ".*"
- "_*"
- "test-*"
- "*-backup.yaml"
With Labels and Annotations
Add metadata to generated Applications:
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
metadata:
name: team-apps
namespace: argocd
spec:
destination:
server: https://kubernetes.default.svc
namespace: argocd
source:
repoURL: https://github.com/myorg/gitops.git
path: teams/platform/apps
labels:
team: platform
managed-by: nyl
environment: production
annotations:
team-slack: "#platform-team"
oncall-pagerduty: "P123ABC"
Multi-Cluster Setup
Generate Applications for an external cluster:
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
metadata:
name: staging-cluster-apps
namespace: argocd
spec:
destination:
server: https://staging-cluster.example.com:6443
namespace: argocd
source:
repoURL: https://github.com/myorg/gitops.git
targetRevision: main
path: clusters/staging
project: staging
syncPolicy:
automated:
selfHeal: true
Note: The cluster must be registered in ArgoCD first.
Multiple Generators
You can have multiple ApplicationGenerators in the same file:
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
metadata:
name: core-apps
spec:
destination:
server: https://kubernetes.default.svc
namespace: argocd
source:
repoURL: https://github.com/myorg/gitops.git
path: core
project: core
---
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
metadata:
name: addon-apps
spec:
destination:
server: https://kubernetes.default.svc
namespace: argocd
source:
repoURL: https://github.com/myorg/gitops.git
path: addons
project: addons
Generated Application Structure
For a NylRelease file like this:
# clusters/default/nginx.yaml
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: NylRelease
metadata:
name: nginx
namespace: web
---
# ... other resources ...
The ApplicationGenerator produces:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nginx # From NylRelease.metadata.name
namespace: argocd # From generator.spec.destination.namespace
labels: # From generator.spec.labels
managed-by: nyl
annotations: # From generator.spec.annotations
docs-url: https://wiki.example.com/apps
spec:
project: default # From generator.spec.project
source:
repoURL: https://github.com/myorg/gitops.git # From generator.spec.source.repoURL
path: clusters/default # Directory of the file
targetRevision: HEAD # From generator.spec.source.targetRevision
plugin:
name: nyl-v2
env:
- name: NYL_CMP_TEMPLATE_INPUT
value: nginx.yaml
- name: NYL_RELEASE_NAME
value: nginx
- name: NYL_RELEASE_NAMESPACE
value: web
destination:
server: https://kubernetes.default.svc # From generator.spec.destination.server
namespace: web # From NylRelease.metadata.namespace
syncPolicy: # From generator.spec.syncPolicy
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Behavior
Processing Flow
-
When
nyl renderencounters an ApplicationGenerator resource:- The ApplicationGenerator is extracted and NOT included in output
- The source path is resolved (from Git by default, or from local override path if configured)
- The resolved source path is scanned for YAML files
- Files are filtered by include/exclude patterns
- Each file is parsed for a NylRelease resource
- An ArgoCD Application is generated for each NylRelease
- Generated Applications are added to the output
-
The ApplicationGenerator acts as an “offline controller” pattern:
- Processing happens during
nyl render - No runtime controller or operator needed
- Deterministic output (same input always produces same output)
- Works in ArgoCD plugin context
- Processing happens during
Path Resolution
By default, source.path / source.paths are resolved using the following precedence:
NYL_APPGEN_REPO_PATH_OVERRIDE, if set- The current local Git checkout, if the current
PWDis inside a Git repository and:- one of the local repository remotes matches
source.repoURLafter normalization source.targetRevisionisHEAD, or exactly matches the current local branch name
- one of the local repository remotes matches
- ArgoCD’s local checkout, when the ArgoCD plugin environment variables match
- Nyl’s normal Git cache/worktree resolution from
source.repoURL+source.targetRevision
If a higher-priority resolution strategy does not match, Nyl falls back to the next one automatically.
Current Local Repository Reuse
When you run nyl render, nyl diff, or nyl apply from inside the same Git repository referenced by an ApplicationGenerator, Nyl can reuse the current local checkout instead of creating a separate clone/worktree.
Reuse is enabled only when all of these conditions are satisfied:
- The current
PWDis inside a Git repository - One of that repository’s remotes matches
source.repoURL(normalized URL comparison) source.targetRevisionisHEAD, or exactly matches the current checked-out branch name
Notes:
- A detached
HEADonly matchestargetRevision: HEAD - Branch names must match exactly
- If these checks fail, Nyl falls back to ArgoCD checkout reuse or normal Git resolution
This is useful for local development because ApplicationGenerator sees the files currently checked out in your working tree.
ArgoCD Local Checkout Reuse
When Nyl runs inside ArgoCD plugin context, it can also reuse ArgoCD’s local checkout instead of cloning:
source.repoURLmust matchARGOCD_APP_SOURCE_REPO_URL(normalized URL comparison)source.targetRevisionmust exactly matchARGOCD_APP_SOURCE_TARGET_REVISIONARGOCD_APP_SOURCE_PATHmust be resolvable to a local source directory
If these checks fail, Nyl falls back to normal Git cache/worktree resolution.
For local testing, set:
export NYL_APPGEN_REPO_PATH_OVERRIDE=/path/to/local/repo
Or use:
export NYL_APPGEN_REPO_PATH_OVERRIDE=@git
to resolve the repository root from the current PWD.
When this env var is set, selectors from source.path/source.paths are resolved under that local directory and no local checkout detection, ArgoCD checkout reuse, clone, or worktree checkout is performed for ApplicationGenerator processing.
If the override path is missing/invalid, @git is used outside a Git repository, or the resulting source path does not exist, nyl render fails with a configuration error.
For generated Applications:
- The
pathfield points to the directory containing the NylRelease file - Relative to the repository root
Validation
ApplicationGenerator is validated when parsed:
spec.destination.servermust not be emptyspec.destination.namespacemust not be emptyspec.source.repoURLmust not be empty- Exactly one of
spec.source.pathorspec.source.pathsmust be set spec.source.path/spec.source.pathsselectors must not be empty
Invalid ApplicationGenerator resources cause nyl render to fail with a clear error message.
Limitations (Phase 1)
Current limitations:
- No templating:
applicationNameTemplateonly supports basic substitution - Single repository: Cannot scan multiple Git repositories in one generator
These limitations are addressed in Phase 2 (future enhancement).
Next Steps
- Bootstrapping Guide - Step-by-step setup
- Best Practices - Production recommendations
Repository Secrets
Nyl automatically discovers credentials from ArgoCD repository secrets to authenticate with private Git repositories. This enables seamless integration with private Helm charts and GitOps repositories.
Overview
When Nyl encounters a Git URL in a HelmChart or ApplicationGenerator resource, it:
- Queries ArgoCD repository secrets from the
argocdnamespace - Matches the Git URL to an appropriate secret
- Uses the discovered credentials for authentication
- Falls back to SSH agent authentication if no secret matches
This approach provides:
- Zero configuration: Credentials are discovered automatically from existing ArgoCD secrets
- Security: Credentials remain in Kubernetes secrets, never in configuration files
- Consistency: Same credentials used by ArgoCD and Nyl
- Flexibility: Supports both SSH and HTTPS authentication
Secret Types
ArgoCD supports two types of repository credential secrets, both of which are supported by Nyl:
Repository Secrets (Fine-Grained)
Repository secrets (argocd.argoproj.io/secret-type=repository) provide credentials for specific repositories using exact URL matching.
Example:
apiVersion: v1
kind: Secret
metadata:
name: myorg-charts-repo
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repository
type: Opaque
stringData:
type: git # Only "git" type secrets are used (defaults to "git" if omitted)
url: git@github.com:myorg/charts.git
sshPrivateKey: |
-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----
Use when:
- Managing credentials for individual repositories
- Need fine-grained access control
- Different credentials for different repos on same host
Repository Credential Templates (Scalable)
Repository credential templates (argocd.argoproj.io/secret-type=repo-creds) provide credentials for multiple repositories matching a URL pattern.
Example:
apiVersion: v1
kind: Secret
metadata:
name: github-myorg-all
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repo-creds
type: Opaque
stringData:
type: git # Only "git" type secrets are used (defaults to "git" if omitted)
url: https://github.com/myorg/*
username: myusername
password: ghp_token123
Pattern Examples:
https://github.com/myorg/*- All repos in organizationhttps://github.com/*- All GitHub reposgit@gitlab.com:*/*- All GitLab repos with org and repo
Use when:
- Managing many repositories from same provider
- Centralized credential management
- Single credential applies to multiple repos
Important: Both secret types must have type: git (or omit the type field, which defaults to git). Secrets with other types (e.g., helm, oci) are ignored.
Authentication Methods
SSH Key Authentication
SSH key authentication is the recommended method for private repositories.
Creating an SSH repository secret:
# Create secret with SSH private key
kubectl create secret generic my-private-repo \
-n argocd \
--from-literal=url=git@github.com:myorg/charts.git \
--from-file=sshPrivateKey=$HOME/.ssh/id_rsa
# Label the secret for ArgoCD
kubectl label secret my-private-repo \
-n argocd \
argocd.argoproj.io/secret-type=repository
Secret structure:
apiVersion: v1
kind: Secret
metadata:
name: my-private-repo
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repository
type: Opaque
data:
url: Z2l0QGdpdGh1Yi5jb206bXlvcmcvY2hhcnRzLmdpdA== # base64: git@github.com:myorg/charts.git
sshPrivateKey: LS0tLS1CRUdJTi... # base64 encoded SSH private key
HTTPS Token Authentication
HTTPS authentication uses personal access tokens or passwords.
Creating an HTTPS repository secret:
# Create secret with username and token
kubectl create secret generic my-private-repo-https \
-n argocd \
--from-literal=url=https://github.com/myorg/charts.git \
--from-literal=username=myusername \
--from-literal=password=ghp_your_token_here
# Label the secret for ArgoCD
kubectl label secret my-private-repo-https \
-n argocd \
argocd.argoproj.io/secret-type=repository
Secret structure:
apiVersion: v1
kind: Secret
metadata:
name: my-private-repo-https
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repository
type: Opaque
data:
url: aHR0cHM6Ly9naXRodWIuY29tL215b3JnL2NoYXJ0cy5naXQ= # base64: https://github.com/myorg/charts.git
username: bXl1c2VybmFtZQ== # base64: myusername
password: Z2hwX3lvdXJfdG9rZW5faGVyZQ== # base64: ghp_your_token_here
SSH Agent Fallback
If no ArgoCD secret matches a Git URL, Nyl automatically falls back to SSH agent authentication for SSH URLs:
# Start SSH agent
eval "$(ssh-agent -s)"
# Add your SSH key
ssh-add ~/.ssh/id_rsa
# Nyl will now use the SSH agent for authentication
nyl render my-chart.yaml
URL Matching
Nyl matches Git URLs to repository secrets using multiple strategies with specific precedence rules.
Credential Selection Precedence
When multiple secrets could apply to a repository, Nyl uses this precedence:
- Exact match (repository secret with exact URL match)
- Pattern match (repo-creds secret with matching pattern)
- Hostname fallback (repository secret with same hostname)
Example:
Given these secrets:
# 1. Exact match (highest precedence)
apiVersion: v1
kind: Secret
metadata:
name: charts-exact
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repository
stringData:
url: https://github.com/myorg/charts.git
sshPrivateKey: |
-----BEGIN OPENSSH PRIVATE KEY-----
...specific key for charts repo...
# 2. Pattern match (medium precedence)
apiVersion: v1
kind: Secret
metadata:
name: myorg-pattern
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repo-creds
stringData:
url: https://github.com/myorg/*
username: myuser
password: ghp_token_for_myorg
# 3. Hostname fallback (lowest precedence)
apiVersion: v1
kind: Secret
metadata:
name: github-fallback
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repository
stringData:
url: https://github.com/other/repo.git
username: myuser
password: ghp_generic_token
For URL https://github.com/myorg/charts.git:
- Uses exact match (#1) ✅
For URL https://github.com/myorg/other-repo.git:
- No exact match
- Uses pattern match (#2) ✅
For URL https://github.com/different-org/repo.git:
- No exact match
- No pattern match
- Uses hostname fallback (#3) ✅
Exact Match
The secret’s url field exactly matches the requested URL (after normalization):
# Secret URL (repository type)
url: https://github.com/myorg/charts.git
# Matches
- https://github.com/myorg/charts.git
- https://github.com/myorg/charts # .git suffix optional
Pattern Match
The secret’s url field contains wildcards (*) that match the requested URL:
# Secret URL (repo-creds type)
url: https://github.com/myorg/*
# Matches
- https://github.com/myorg/charts.git
- https://github.com/myorg/other-repo.git
- https://github.com/myorg/any-repo
# Does NOT match
- https://github.com/otherorg/repo.git # Different organization
- https://gitlab.com/myorg/repo.git # Different host
Pattern Examples:
# Organization-level access
url: https://github.com/myorg/*
# Matches: https://github.com/myorg/repo1, https://github.com/myorg/repo2
# Host-level access (all repos on GitHub)
url: https://github.com/*
# Matches: https://github.com/anyorg/anyrepo
# SSH with pattern
url: git@github.com:myorg/*
# Matches: git@github.com:myorg/charts, git@github.com:myorg/other
# Multi-level wildcard
url: https://gitlab.com/*/*
# Matches: https://gitlab.com/group/project, https://gitlab.com/org/repo
Hostname Fallback
If no exact or pattern match is found, Nyl matches by hostname. This allows a single repository secret to authenticate all repositories on the same host:
# Secret URL (repository type)
url: https://github.com/myorg/charts.git
# Also matches (same hostname, no exact/pattern match)
- https://github.com/myorg/other-repo.git
- https://github.com/another-org/repo.git
URL Normalization
URLs are normalized before matching:
- Case-insensitive comparison
.gitsuffix is optional- SSH shorthand converted to full URL:
git@github.com:org/repo→ssh://git@github.com/org/repo
- Trailing slashes removed
This means these URLs are considered equivalent:
https://GitHub.com/MyOrg/Repo.githttps://github.com/myorg/repogit@github.com:myorg/repo(when matching SSH patterns)
Examples
Private Helm Chart
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: my-app
namespace: default
spec:
chart:
repository: git+git@github.com:myorg/private-charts.git
version: main
name: charts/my-app
Nyl will:
- Look for ArgoCD secret with URL
git@github.com:myorg/private-charts.git - If not found, look for secret with hostname
github.com - If not found, try SSH agent
- Clone repository and extract chart from
charts/my-app
Private ApplicationGenerator
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
metadata:
name: microservices
spec:
destination:
server: https://kubernetes.default.svc
namespace: argocd
source:
repoURL: https://github.com/myorg/gitops-repo.git
targetRevision: main
path: apps
Nyl will:
- Look for ArgoCD secret with URL
https://github.com/myorg/gitops-repo.git - If found, use credentials to clone repository
- Scan
apps/directory for application definitions - Generate ArgoCD
Applicationresources
Troubleshooting
Authentication Failures
Error: Authentication failed for git@github.com:myorg/repo.git: Failed to use SSH agent
Solutions:
- Create an ArgoCD repository secret with SSH key
- Start SSH agent and add your key:
ssh-add ~/.ssh/id_rsa - Verify SSH key has access to the repository
Error: No credentials found for repository: https://github.com/myorg/repo.git
Solutions:
- Create an ArgoCD repository secret for this URL
- Verify secret has label
argocd.argoproj.io/secret-type=repository - Check secret is in
argocdnamespace
Permission Issues
Error: Failed to query ArgoCD secrets: Forbidden
Solutions:
- Verify Nyl has RBAC permissions to read secrets in
argocdnamespace - Add ClusterRole/Role with
get,listpermissions on secrets - Bind role to Nyl service account
URL Matching Issues
If credentials aren’t being discovered:
- Check URL format matches between secret and chart
- Verify secret URL is normalized (lowercase, no trailing slash)
- Test with exact URL match before relying on hostname fallback
- Check secret has the correct label
Debugging
Enable debug logging to see credential discovery:
RUST_LOG=debug nyl render my-chart.yaml
Look for log messages:
Discovering credentials from ArgoCD secretsFound credential for URL: ...No credential found, falling back to SSH agent
Best Practices
- Use SSH keys: Preferred over HTTPS tokens for better security
- Choose the right secret type:
- Use
repo-credsfor organization-wide access (scalable) - Use
repositoryfor specific repos needing different credentials - Combine both types for flexibility with proper precedence
- Use
- Pattern-based credentials: Use
repo-credswith patterns likehttps://github.com/myorg/*to centrally manage credentials for multiple repositories - Leverage precedence: Use exact match
repositorysecrets to override broaderrepo-credspatterns for specific repos - Rotate credentials: Update secrets regularly and test after rotation
- Least privilege: Ensure SSH keys/tokens have minimal required permissions
- Monitor access: Review ArgoCD secret access logs periodically
- Document secrets: Maintain inventory of repository secrets, their patterns, and their purpose
Example Setup:
# 1. Organization-wide access (repo-creds)
apiVersion: v1
kind: Secret
metadata:
name: github-myorg-default
labels:
argocd.argoproj.io/secret-type: repo-creds
stringData:
type: git
url: https://github.com/myorg/*
username: ci-bot
password: ghp_org_token
---
# 2. Specific repo with different credentials (repository)
apiVersion: v1
kind: Secret
metadata:
name: sensitive-repo-override
labels:
argocd.argoproj.io/secret-type: repository
stringData:
type: git
url: https://github.com/myorg/sensitive-repo.git
sshPrivateKey: |
-----BEGIN OPENSSH PRIVATE KEY-----
...dedicated SSH key for sensitive repo...
This setup provides:
- Default credentials for all
myorgrepositories via pattern match - Override with dedicated SSH key for
sensitive-repovia exact match - Separation of concerns and least privilege access
Kubernetes RBAC
Nyl requires these permissions to discover repository secrets:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nyl-secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"]
# Optionally scope to argocd namespace
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: nyl-secret-reader
subjects:
- kind: ServiceAccount
name: nyl
namespace: argocd
roleRef:
kind: ClusterRole
name: nyl-secret-reader
apiGroup: rbac.authorization.k8s.io
Security Considerations
- Credential storage: Secrets are stored in Kubernetes, encrypted at rest
- Access control: RBAC limits who can read repository secrets
- Credential scope: Each secret applies only to specific repositories
- No plaintext: Credentials never appear in logs or configuration files
- SSH keys: Use dedicated deploy keys with read-only access
- Token rotation: Update tokens/keys regularly and monitor for leaks
Best Practices
This guide covers recommended patterns and best practices for using Nyl with ArgoCD in production environments.
Directory Structure
Recommended Layout
Organize your GitOps repository with clear separation of concerns:
gitops-repo/
├── bootstrap/
│ └── bootstrap.yaml # Initial bootstrap manifest
├── argocd/
│ └── argocd.yaml # ArgoCD installation
├── apps.yaml # ApplicationGenerator for apps
├── clusters/
│ ├── production/
│ │ ├── app1.yaml
│ │ ├── app2.yaml
│ │ └── app3.yaml
│ ├── staging/
│ │ ├── app1.yaml
│ │ └── app2.yaml
│ └── development/
│ └── app1.yaml
├── base/ # Shared base configurations
│ ├── postgres/
│ └── redis/
├── nyl.toml
└── nyl-secrets.yaml
Directory Organization Strategies
By Environment:
clusters/
├── production/
├── staging/
└── development/
- Clear separation of environments
- Easy to apply different policies per environment
- Suitable for ApplicationGenerator scanning
By Team:
teams/
├── platform/
├── data/
└── ml/
- Delegation to team-specific directories
- Team-level RBAC with ArgoCD Projects
- Enables team autonomy
By Application Type:
├── core-services/
├── data-services/
├── monitoring/
└── security/
- Logical grouping by function
- Useful for dependency management
- Clear categorization
Hybrid Approach (Recommended):
├── core/ # Critical platform services
│ ├── argocd/
│ ├── cert-manager/
│ └── ingress-nginx/
├── clusters/
│ ├── production/
│ │ ├── team-a/
│ │ └── team-b/
│ └── staging/
│ ├── team-a/
│ └── team-b/
└── shared/ # Shared configurations
├── postgres/
└── redis/
ApplicationGenerator Patterns
One Generator Per Environment
Create separate ApplicationGenerators for each environment:
# apps-production.yaml
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
metadata:
name: production-apps
spec:
destination:
server: https://kubernetes.default.svc
namespace: argocd
source:
repoURL: https://github.com/myorg/gitops.git
targetRevision: main
path: clusters/production
project: production
syncPolicy:
automated:
prune: true
selfHeal: true
labels:
environment: production
---
# apps-staging.yaml
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
metadata:
name: staging-apps
spec:
destination:
server: https://kubernetes.default.svc
namespace: argocd
source:
repoURL: https://github.com/myorg/gitops.git
targetRevision: main
path: clusters/staging
project: staging
syncPolicy:
automated:
selfHeal: true # Note: No prune in staging
labels:
environment: staging
One App Per File
Each application should have its own YAML file:
clusters/production/
├── nginx.yaml # One app
├── postgres.yaml # One app
└── redis.yaml # One app
Benefits:
- Clear ownership and Git history per app
- Easy to move apps between environments
- Better for code review
- Simpler conflict resolution
Avoid putting multiple apps in one file (unless they’re tightly coupled).
Exclude Patterns
Use exclude patterns to prevent accidental Application generation:
spec:
source:
exclude:
- ".*" # Hidden files
- "_*" # Underscore prefix (templates, WIP)
- "*.backup" # Backup files
- "test-*" # Test files
- "apps.yaml" # The generator itself
- "README.md" # Documentation
Sync Policies
Production Environments
For production, be conservative:
syncPolicy:
automated:
prune: true # Delete removed resources
selfHeal: true # Force Git state
syncOptions:
- CreateNamespace=true
- PruneLast=true # Prune after other operations
Why?
prune: true: Ensures removed manifests are deletedselfHeal: true: Prevents manual changes (drift detection)PruneLast: Safer deletion order
Staging/Development Environments
For non-production, allow more flexibility:
syncPolicy:
automated:
prune: false # Don't auto-delete (allow manual testing)
selfHeal: true # Still enforce Git state
Why?
prune: false: Allows temporary manual resources for testingselfHeal: true: Still prevents accidental drift
Manual Sync for Critical Services
For very critical services (databases, auth), consider manual sync:
# No automated syncPolicy
# Sync manually via ArgoCD UI or CLI
Secret Management
Option 1: Sealed Secrets
Use Bitnami Sealed Secrets for encrypted secrets in Git:
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: NylRelease
metadata:
name: app
namespace: default
---
apiVersion: v1
kind: SealedSecret
metadata:
name: app-secrets
namespace: default
spec:
encryptedData:
password: AgBjW8X... # Encrypted data safe for Git
Option 2: External Secrets Operator
Sync secrets from external providers (AWS Secrets Manager, Vault, etc.):
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
namespace: default
spec:
secretStoreRef:
name: aws-secrets-manager
target:
name: app-secrets
data:
- secretKey: password
remoteRef:
key: /app/password
Option 3: Nyl Secrets (SOPS)
Use Nyl’s built-in SOPS integration:
# nyl-secrets.yaml
type: sops
path: ./.secrets.yaml
# .secrets.yaml (encrypted with SOPS)
database:
password: ENC[AES256_GCM,data:xxx,iv:yyy,tag:zzz]
Never commit plaintext secrets to Git!
Multi-Cluster Setup
Hub-and-Spoke Model
Manage multiple clusters from a central ArgoCD instance:
# apps-prod-cluster1.yaml
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
metadata:
name: prod-cluster1-apps
spec:
destination:
server: https://cluster1.example.com:6443
namespace: argocd
source:
repoURL: https://github.com/myorg/gitops.git
path: clusters/cluster1
labels:
cluster: cluster1
---
# apps-prod-cluster2.yaml
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
metadata:
name: prod-cluster2-apps
spec:
destination:
server: https://cluster2.example.com:6443
namespace: argocd
source:
repoURL: https://github.com/myorg/gitops.git
path: clusters/cluster2
labels:
cluster: cluster2
Register clusters in ArgoCD:
argocd cluster add cluster1-context
argocd cluster add cluster2-context
Per-Cluster ArgoCD
Each cluster has its own ArgoCD instance:
gitops-repo/
├── clusters/
│ ├── prod-us-east/
│ │ ├── argocd/
│ │ ├── apps.yaml # ApplicationGenerator for this cluster
│ │ └── apps/
│ └── prod-eu-west/
│ ├── argocd/
│ ├── apps.yaml
│ └── apps/
Each cluster bootstraps independently.
Monitoring and Observability
Prometheus Metrics
ArgoCD exports metrics; monitor these key indicators:
argocd_app_info: Application statusargocd_app_sync_total: Sync operationsargocd_app_health_status: Health status
Alerts
Set up alerts for:
# OutOfSync alert
- alert: ArgoCDAppOutOfSync
expr: argocd_app_sync_status{sync_status="OutOfSync"} == 1
for: 15m
labels:
severity: warning
# Degraded health
- alert: ArgoCDAppUnhealthy
expr: argocd_app_health_status{health_status!="Healthy"} == 1
for: 10m
labels:
severity: critical
Notifications
Configure ArgoCD notifications for Slack, email, or PagerDuty:
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
namespace: argocd
data:
service.slack: |
token: $slack-token
template.app-sync-status: |
message: Application {{.app.metadata.name}} sync is {{.app.status.sync.status}}
trigger.on-sync-failed: |
- when: app.status.sync.status == 'Failed'
send: [app-sync-status]
CI/CD Integration
Validation Pipeline
Add CI checks to validate Nyl manifests before merge:
# .github/workflows/validate.yml
name: Validate Manifests
on: [pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Nyl
run: |
curl -L https://github.com/NiklasRosenstein/nyl/releases/latest/download/nyl-linux-amd64 \
-o /usr/local/bin/nyl
chmod +x /usr/local/bin/nyl
- name: Validate
run: nyl validate .
- name: Render
run: nyl render apps.yaml > /dev/null
Dry-Run Rendering
Test ApplicationGenerator output in CI:
# Render and check output
nyl render apps.yaml > output.yaml
# Verify Applications were generated
cat output.yaml | grep "kind: Application" | wc -l
# Check specific apps exist
grep "name: nginx" output.yaml
grep "name: postgres" output.yaml
Preview Environments
Create preview environments for PRs:
# .github/workflows/preview.yml
name: Preview Environment
on: [pull_request]
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy Preview
run: |
# Create preview namespace
kubectl create namespace pr-${{ github.event.pull_request.number }}
# Render manifests with preview profile
nyl render -e preview apps.yaml | kubectl apply -n pr-${{ github.event.pull_request.number }} -f -
Performance Considerations
Repository Size
Keep repositories focused:
- Separate infrastructure repo from application repos
- Use Git submodules for shared configurations if needed
- Archive old applications to separate branches
Sync Frequency
Adjust sync frequency based on needs:
# Faster sync for development
metadata:
annotations:
argocd.argoproj.io/sync-options: "Timeout=120"
# Slower sync for large apps
metadata:
annotations:
argocd.argoproj.io/sync-options: "Timeout=600"
Resource Limits
Set appropriate resource limits for ArgoCD components:
spec:
values:
repoServer:
resources:
limits:
cpu: "1000m"
memory: "1Gi"
requests:
cpu: "500m"
memory: "512Mi"
Disaster Recovery
Backup Strategy
- Git is Source of Truth: All manifests in Git
- Backup ArgoCD Configuration:
kubectl get applications -n argocd -o yaml > argocd-apps-backup.yaml kubectl get appprojects -n argocd -o yaml > argocd-projects-backup.yaml - Cluster Backups: Use Velero or similar for cluster state
Recovery Procedure
- Restore cluster from backup (if needed)
- Re-apply bootstrap manifest:
kubectl apply -f bootstrap.yaml - ArgoCD recreates all Applications from Git
- Applications sync and restore workloads
Why this works: Git is the source of truth; ArgoCD recreates everything from Git automatically.
Security
RBAC with Projects
Use ArgoCD Projects for RBAC:
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: team-a
namespace: argocd
spec:
description: Team A applications
sourceRepos:
- https://github.com/myorg/gitops.git
destinations:
- namespace: team-a-*
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: ''
kind: Namespace
namespaceResourceWhitelist:
- group: '*'
kind: '*'
Signed Commits
Require GPG-signed commits for production:
spec:
source:
repoURL: https://github.com/myorg/gitops.git
targetRevision: main
# In ArgoCD settings
metadata:
annotations:
argocd.argoproj.io/verify-signature: "true"
Least Privilege
- ArgoCD service account should have minimal permissions
- Use separate service accounts per ApplicationGenerator/Project
- Audit ArgoCD RBAC regularly
Troubleshooting
Common Issues
Issue: Applications not syncing
- Check: ArgoCD repo-server logs
- Fix: Ensure Nyl plugin is installed and accessible
Issue: Wrong Applications generated
- Check: Render manifests locally:
nyl render apps.yaml - Fix: Adjust include/exclude patterns
Issue: Sync timeouts
- Check: Large Helm charts or slow cluster
- Fix: Increase timeout annotation
Debug Commands
# View ApplicationGenerator output
nyl render apps.yaml
# Check ArgoCD Application
argocd app get <app-name>
# View rendered manifests
argocd app manifests <app-name>
# Force refresh
argocd app get <app-name> --refresh
# View repo-server logs
kubectl logs deployment/argocd-repo-server -n argocd -f
Summary
Key takeaways:
- ✅ Use clear directory structure
- ✅ One ApplicationGenerator per environment
- ✅ One app per file
- ✅ Configure appropriate sync policies
- ✅ Never commit plaintext secrets
- ✅ Monitor and alert on sync status
- ✅ Validate in CI before merge
- ✅ Git is your source of truth
Following these practices ensures reliable, secure, and scalable GitOps with Nyl and ArgoCD.
Resources
Nyl provides Kubernetes-style custom resources for declarative configuration and deployment.
Resource Types
Core Resources
- NylRelease: Defines release metadata (name, namespace) for deployments
- Component: Compact chart-backed resource using dynamic
kindlookup - HelmChart: Declarative Helm chart deployment with templating support
- RemoteManifest: Fetch and include manifests from a remote HTTPS URL
ArgoCD Resources
- ApplicationGenerator: Automatically generates ArgoCD Applications from NylRelease files
Policy Resources
- Kyverno Policies: Apply Kyverno mutation and validation policies at render time
Resource Format
All Nyl resources follow Kubernetes resource conventions:
apiVersion: <api-version>
kind: <resource-kind>
metadata:
name: <name>
namespace: <namespace> # Optional
spec:
# Resource-specific fields
API Versions
nyl.niklasrosenstein.github.com/v1: Core Nyl resources (NylRelease, HelmChart)- Includes:
NylRelease,HelmChart,RemoteManifest
- Includes:
argocd.nyl.niklasrosenstein.github.com/v1: ArgoCD integration resources (ApplicationGenerator)components.nyl.niklasrosenstein.github.com/v1: Component resources (dynamickindpath/shortcut)
Processing Behavior
Regular Kubernetes Resources
Regular Kubernetes resources (ConfigMap, Deployment, etc.) are passed through unchanged during nyl render.
Nyl Resources
Nyl resources are processed based on their kind:
- NylRelease: Extracted and removed from output (provides metadata only)
- Component: Resolved to a chart reference and rendered via Helm, replaced with rendered manifests
- HelmChart: Rendered using Helm templating, replaced with rendered manifests
- RemoteManifest: Fetched via HTTPS and parsed into documents, then processed recursively
- ApplicationGenerator: Processed to generate ArgoCD Applications, removed from output
Multi-Document Files
Nyl supports YAML multi-document files with --- separators:
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: NylRelease
metadata:
name: myapp
namespace: default
---
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
data:
key: value
---
apiVersion: v1
kind: Service
metadata:
name: myapp-svc
spec:
ports:
- port: 80
Processing:
- NylRelease is extracted (provides name and namespace)
- ConfigMap and Service are output as-is
See Also
Component
Component is a chart-backed resource format identified by:
apiVersion: components.nyl.niklasrosenstein.github.com/v1- dynamic
kind(not a fixed literal)
Unlike fixed resource kinds, kind encodes either:
- a local component path (
<apiVersion>/<kind>) - a remote shortcut (
<base>[#<name>][@<version>])
Resource Shape
apiVersion: components.nyl.niklasrosenstein.github.com/v1
kind: example/v1/Nginx
metadata:
name: web
namespace: default
spec:
replicaCount: 2
Semantics
kind: chart target (local or remote shortcut)metadata.name: Helm release namemetadata.namespace: Helm release namespace (defaults todefault)spec: Helm values payload
Local Resolution
With:
[project]
components_search_paths = ["components"]
kind: example/v1/Nginx resolves to:
components/example/v1/Nginx/Chart.yaml
Nyl checks components_search_paths in order and uses the first match.
Alias Mapping
project.aliases can map regular resource types to component targets:
[project.aliases]
"myapi.io/v1/MyKind" = "oci://registry-1.docker.io/bitnamicharts/nginx@18.2.4"
When an alias matches, Nyl resolves directly to the alias target.
Remote Shortcut Parsing
Shortcut format:
<base>[#<name>][@<version>]
Remote bases:
http://orhttps://oci://git+
For complete shortcut examples and guidance, see Remote Shortcuts & Aliases.
HelmChart
The HelmChart resource enables declarative Helm chart deployment with templating support. Charts can be referenced from local paths, chart names, or Git repositories.
Use HelmChart when you want explicit chart fields in spec.chart.*.
Use Component when you want compact chart-backed resources with dynamic kind and optional alias indirection.
Note: Git chart references are fully supported. Repositories are cloned automatically to a local cache. See the Git Integration guide for details.
Related: Components
Component resources provide a compact chart-backed format and support local component paths, remote shortcut syntax, and alias-based indirection.
For component syntax, examples, and pattern selection, see:
Resource Definition
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: string # Helm release name
namespace: string # Target namespace (optional, defaults to "default")
spec:
chart: # Chart reference (choose one method)
# Universal fields:
repository: string # Repository URL (Git, OCI, or Helm)
name: string # Universal name field (context-dependent)
version: string # Chart version or Git reference
# Repository types (indicated by protocol prefix):
# - Git: repository starts with "git+" (e.g., "git+https://...")
# - OCI: repository starts with "oci://" (e.g., "oci://ghcr.io/...")
# - Helm: plain HTTPS URL (e.g., "https://charts.example.com")
# - Local: no repository, name is filesystem path
values: object # Chart values (merged with profile values)
Chart Reference Methods
Local Path
Reference a chart by filesystem path (absolute or relative) using the name field:
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: nginx
namespace: default
spec:
chart:
name: ./charts/nginx
Chart Name
Reference a chart by name (without path separators), searched in configured search paths:
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: nginx
namespace: default
spec:
chart:
name: nginx
Configure search paths in nyl.toml:
[project]
helm_chart_search_paths = ["./charts", "/opt/helm-charts"]
Git Repository
Reference a chart from a Git repository using the git+ protocol prefix:
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: nginx
namespace: default
spec:
chart:
repository: git+https://github.com/bitnami/charts.git
version: main
name: bitnami/nginx
Git Parameters:
repository(required): Git repository URL withgit+prefix (HTTPS or SSH)version(optional): Branch, tag, or commit SHA (default:HEAD)name(optional): Subdirectory within the repository containing the chart
Helm Dependencies:
Charts from Git repositories with dependencies are automatically handled. If your chart has a Chart.yaml with dependencies or a Chart.lock file, Nyl will automatically run helm dependency build to fetch and build the dependencies before rendering the chart.
Examples:
# Latest from main branch
chart:
repository: git+https://github.com/example/charts.git
version: main
name: charts/myapp
# Specific version tag
chart:
repository: git+https://github.com/example/charts.git
version: v2.1.0
name: charts/myapp
# Specific commit
chart:
repository: git+https://github.com/example/charts.git
version: abc123def456
name: charts/myapp
# Root of repository (no subpath)
chart:
repository: git+https://github.com/example/simple-chart.git
version: main
# SSH URL
chart:
repository: git+git@github.com:example/charts.git
version: main
name: charts/myapp
See Git Integration for more details on Git support.
Release Configuration
The Helm release is configured via the metadata fields:
metadata:
name: myapp # Helm release name
namespace: production # Target namespace
Defaults:
namespace: Usesdefaultif not specified
Creating Namespaces
If you need to create the namespace before deploying the chart, add a Namespace resource:
apiVersion: v1
kind: Namespace
metadata:
name: production
---
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: myapp
namespace: production
spec:
chart:
name: ./charts/myapp
values:
replicas: 3
When using ArgoCD, you can alternatively enable automatic namespace creation:
apiVersion: argoproj.io/v1alpha1
kind: Application
spec:
syncPolicy:
syncOptions:
- CreateNamespace=true
Values
Chart values can be provided in multiple ways:
Inline Values
spec:
values:
image:
repository: nginx
tag: "1.25"
replicas: 3
service:
type: LoadBalancer
Profile Values
Values from the active profile are automatically merged:
[profile.values.production]
replicas = 5
[profile.values.production.resources.requests]
cpu = "500m"
memory = "512Mi"
Inline values take precedence over profile values.
Templating in Values
Values support Jinja2 templating:
spec:
values:
image:
tag: "{{ env.NYL_IMAGE_TAG }}"
environment: "{{ profile }}"
Complete Example
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: NylRelease
metadata:
name: myapp
namespace: production
---
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: myapp
namespace: production
spec:
chart:
repository: git+https://github.com/company/charts.git
version: v2.1.0
name: applications/myapp
values:
replicas: 3
image:
repository: company/myapp
tag: "{{ env.VERSION }}"
ingress:
enabled: true
host: myapp.example.com
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
Multi-Environment Deployments
Use the same chart with different values per environment:
# base manifest
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: myapp
spec:
chart:
repository: git+https://github.com/company/charts.git
version: stable
name: myapp
values:
# Base values here
[profile.values.development]
replicas = 1
environment = "development"
[profile.values.development.image]
tag = "latest"
[profile.values.production]
replicas = 5
environment = "production"
[profile.values.production.image]
tag = "v2.1.0"
[profile.values.production.resources.requests]
cpu = "1000m"
Render for specific environment:
nyl render --profile production app.yaml
See Also
- Git Integration - Git repository management
- Configuration - Search paths and settings
- NylRelease - Release metadata
RemoteManifest
RemoteManifest fetches YAML/JSON documents from a remote HTTPS URL and feeds
them into Nyl’s normal render pipeline.
API Version
nyl.niklasrosenstein.github.com/v1
Schema
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: RemoteManifest
metadata:
name: <name>
spec:
url: https://example.com/path/manifests.yaml
overrideNamespace: false
Fields
spec.url(required): HTTPS URL containing one or more YAML/JSON documents.spec.overrideNamespace(optional, defaultfalse): whentrue, fetched resources that already havemetadata.namespacewill have that value replaced withRemoteManifest.metadata.namespace.
Behavior
- URL must use
https://. - Fetching uses Nyl’s native HTTPS client (no shell-out), with HTTPS-only redirect policy.
- Request timeouts are enforced (connect: 5s, total: 30s).
- Response size is limited to 30 MiB; larger payloads fail fast.
- Content is parsed as YAML multi-document stream.
- Parsed resources are processed recursively like local resources.
- Remote content is not rendered as a Jinja template.
- When
spec.overrideNamespace: true, remote manifests withmetadata.namespaceare rewritten toRemoteManifest.metadata.namespace. - Special case: for
RoleBindingandClusterRoleBinding(rbac.authorization.k8s.io/*),subjects[*].namespaceis also rewritten (ServiceAccount subjects are forced to the override namespace). - Potential future rewrite targets (currently not handled): webhook service namespaces (
MutatingWebhookConfiguration,ValidatingWebhookConfiguration, CRD conversion webhook), andAPIService.spec.service.namespace. - Fetch or parse failures stop the command (
render,diff,apply).
Example
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: RemoteManifest
metadata:
name: shared-crds
spec:
url: https://example.com/platform/crds.yaml
NylRelease
The NylRelease resource specifies release metadata (name and namespace) for a deployment. It’s an optional resource that provides context for rendering manifests.
Resource Definition
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: NylRelease
metadata:
name: string # Release name
namespace: string # Target namespace
spec: {} # Reserved for future use
argocd: # Optional ArgoCD customization
applicationOverride: {} # Partial Application override (object)
Field Reference
metadata.name
- Type:
string(required) - Description: The release name
- Usage: Used as the Helm release name when rendering HelmCharts
metadata.namespace
- Type:
string(required) - Description: The target namespace for this release
- Usage: Resources are deployed to this namespace
spec.argocd.applicationOverride
- Type:
object(optional) - Description: Partial ArgoCD
Applicationoverride for use withApplicationGeneratorrelease customization policy - Behavior:
- Override fields are applied only if allowed by
ApplicationGenerator.spec.releaseCustomization - Plain keys replace the generated
Applicationvalue at that field path - Keys prefixed with
+append to list-valuedApplicationfields instead of replacing them +<field>uses the canonical field path without+for allow/deny checks- Unsupported or disallowed fields are ignored
- Invalid append operations (for example using
+on a non-list field or with a non-list value) are ignored - Ignored fields are reported as a warning in generated
Application.spec.info
- Override fields are applied only if allowed by
Behavior
During Rendering
When nyl render encounters a NylRelease:
- The resource is extracted from the file
- Metadata is used for rendering other resources in the file
- The NylRelease itself is not included in the output
With ApplicationGenerator
When ApplicationGenerator scans files:
- Files with a NylRelease are discovered
- An ArgoCD Application is generated per NylRelease
- Application metadata comes from the NylRelease
Singleton Constraint
Only one NylRelease is allowed per file. Multiple NylReleases in the same file will cause an error:
Error: Multiple NylRelease resources found in file
Examples
Minimal NylRelease
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: NylRelease
metadata:
name: myapp
namespace: default
With Other Resources
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: NylRelease
metadata:
name: myapp
namespace: production
---
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
namespace: production
data:
environment: production
version: "1.0.0"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:1.0.0
With HelmChart
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: NylRelease
metadata:
name: nginx
namespace: web
---
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: nginx
namespace: web
spec:
chart:
repository: https://charts.bitnami.com/bitnami
name: nginx
version: "15.4.4"
values:
replicaCount: 2
Use Cases
ArgoCD Integration
NylRelease is primarily used for ArgoCD integration:
# app.yaml in Git repository
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: NylRelease
metadata:
name: myapp
namespace: production
---
# ... other resources ...
ArgoCD Application:
apiVersion: argoproj.io/v1alpha1
kind: Application
spec:
source:
plugin:
name: nyl-v2
env:
- name: NYL_RELEASE_NAME
value: myapp # From NylRelease.metadata.name
- name: NYL_RELEASE_NAMESPACE
value: production # From NylRelease.metadata.namespace
Appending Sync Options
applicationOverride can append to list-valued Application fields by prefixing the field name with +.
apiVersion: nyl.niklasrosenstein.github.com/v1
kind: NylRelease
metadata:
name: service-proxies
namespace: home-proxy
spec:
argocd:
applicationOverride:
spec:
syncPolicy:
+syncOptions:
- RespectIgnoreDifferences=false
When combined with an ApplicationGenerator default such as:
syncPolicy:
syncOptions:
- ServerSideApply=true
- ApplyOutOfSyncOnly=true
the generated ArgoCD Application contains all three entries in order:
spec:
syncPolicy:
syncOptions:
- ServerSideApply=true
- ApplyOutOfSyncOnly=true
- RespectIgnoreDifferences=false
ApplicationGenerator Discovery
ApplicationGenerator scans for NylRelease resources:
# apps.yaml
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
spec:
source:
path: clusters/default # Scans this directory
For each file in clusters/default/ with a NylRelease, an Application is generated.
Metadata Propagation
NylRelease metadata can be used in templates (future enhancement):
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .release.name }}-config
namespace: {{ .release.namespace }}
data:
release_name: {{ .release.name }}
Validation
Required Fields
metadata.namemust be specifiedmetadata.namespacemust be specified
Missing fields cause validation errors:
Error: Invalid NylRelease resource: missing field `metadata.name`
Valid Names
Names must follow Kubernetes naming conventions:
- Lowercase alphanumeric characters,
-,. - Start and end with alphanumeric
- Max 253 characters
When Not to Use
You don’t need NylRelease if:
- Deploying plain Kubernetes manifests without Helm
- Not using ArgoCD integration
- Rendering manifests locally without release context
In these cases, just use regular Kubernetes resources directly.
See Also
- ApplicationGenerator
- ArgoCD Bootstrapping
- HelmChart Resource (future documentation)
ApplicationGenerator Resource Reference
For detailed ApplicationGenerator documentation, see:
ArgoCD ApplicationGenerator Guide
Quick Reference
apiVersion: argocd.nyl.niklasrosenstein.github.com/v1
kind: ApplicationGenerator
metadata:
name: cluster-apps
namespace: argocd
spec:
destination:
server: https://kubernetes.default.svc
namespace: argocd
source:
repoURL: https://github.com/org/repo.git
targetRevision: HEAD
path: clusters/default # Exactly one of path or paths is required
# paths: ["clusters/*/apps", "shared/apps/*.yaml"]
project: default
syncPolicy:
automated:
prune: true
selfHeal: true
releaseCustomization:
allowedPaths:
- metadata.annotations."pref.argocd.argoproj.io/*"
- spec.info.**
- spec.ignoreDifferences.**
- spec.syncPolicy.**
deniedPaths:
- spec.syncPolicy.automated.prune
The ApplicationGenerator resource enables automatic generation of ArgoCD Applications from NylRelease files in a directory.
Key behavior:
source.pathandsource.pathsare mutually exclusive.- A directory selector is scanned non-recursively by default.
- Use glob selectors in
path/pathswhen you want recursive discovery. include/excludepatterns are matched against file paths relative to the repository root.- Source resolution can reuse a matching current local Git checkout before falling back to ArgoCD checkout reuse or Nyl’s Git cache/worktree flow.
NylRelease.spec.argocd.applicationOverrideis always evaluated againstallowedPaths/deniedPaths.allowedPaths/deniedPathsuse dotted globs where*matches one segment and**matches multiple segments.- If both allow and deny match, deny wins. Ignored fields are reported in generated
Application.spec.info.
See the full guide for complete field reference, examples, and usage patterns.
Kyverno Policies
Nyl supports applying Kyverno policies to Kubernetes manifests at render time. This allows you to mutate and validate resources before they are applied to the cluster.
Overview
Kyverno policies in Nyl are standard Kyverno CRDs annotated with a scope that determines when and where they are applied during rendering. Policies without the scope annotation are treated as normal Kubernetes resources and passed through to the output.
Quick Start
apiVersion: policies.kyverno.io/v1
kind: MutatingPolicy
metadata:
name: add-managed-by-label
annotations:
nyl.niklasrosenstein.github.com/apply-policy-scope: Global
spec:
matchConstraints:
resourceRules:
- apiGroups: ['']
apiVersions: ['v1']
operations: ['CREATE']
resources: ['configmaps', 'services']
mutations:
- patchType: ApplyConfiguration
applyConfiguration:
expression: >-
Object{metadata: Object.metadata{labels: Object.metadata.labels{managedBy: "nyl"}}}
Supported Policy Types
Nyl supports standard Kyverno policy CRDs:
Working Policy Types
These policy types work with kyverno apply in offline mode:
- ClusterPolicy (
kyverno.io/v1) - Cluster-wide policies using traditional Kyverno format - MutatingPolicy (
policies.kyverno.io/v1) - Namespace or cluster-scoped mutation policies - ValidatingPolicy (
policies.kyverno.io/v1) - Namespace or cluster-scoped validation policies
Detected but Not Evaluable
These policy types are detected but require a live Kubernetes cluster:
- GeneratingPolicy (
policies.kyverno.io/v1) - Generate new resources - DeletingPolicy (
policies.kyverno.io/v1) - Delete resources - ImageValidatingPolicy (
policies.kyverno.io/v1) - Validate container images
Scope Annotation
Policies must include the nyl.niklasrosenstein.github.com/apply-policy-scope annotation to be processed by Nyl.
Available Scopes
Global (Currently Supported)
Applies to all resources in the rendered file. Since Nyl processes single files, this means all resources from the manifest being rendered.
metadata:
annotations:
nyl.niklasrosenstein.github.com/apply-policy-scope: Global
Use cases:
- Organization-wide labeling standards
- Security policies applied to all resources
- Compliance requirements
Future Scopes (Not Yet Implemented)
- Subtree: Applies to siblings and all descendant resources
- Immediate: Applies to sibling resources only
Policies with these scopes will trigger a warning but won’t cause errors.
Policy Examples
Mutation Policy
Add labels to all ConfigMaps and Secrets:
apiVersion: policies.kyverno.io/v1
kind: MutatingPolicy
metadata:
name: add-labels
annotations:
nyl.niklasrosenstein.github.com/apply-policy-scope: Global
spec:
matchConstraints:
resourceRules:
- apiGroups: ['']
apiVersions: ['v1']
operations: ['CREATE']
resources: ['configmaps', 'secrets']
mutations:
- patchType: ApplyConfiguration
applyConfiguration:
expression: >-
Object{
metadata: Object.metadata{
labels: Object.metadata.labels{
environment: "production",
managedBy: "nyl"
}
}
}
Validation Policy
Require specific labels on all resources:
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: require-labels
annotations:
nyl.niklasrosenstein.github.com/apply-policy-scope: Global
spec:
matchConstraints:
resourceRules:
- apiGroups: ['*']
apiVersions: ['*']
operations: ['CREATE']
resources: ['*']
validations:
- expression: >-
has(object.metadata.labels) &&
'environment' in object.metadata.labels &&
'team' in object.metadata.labels
message: "All resources must have 'environment' and 'team' labels"
ClusterPolicy (Traditional Format)
Using the older kyverno.io/v1 ClusterPolicy format:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-default-network-policy
annotations:
nyl.niklasrosenstein.github.com/apply-policy-scope: Global
spec:
rules:
- name: add-network-policy-annotation
match:
any:
- resources:
kinds:
- Namespace
mutate:
patchStrategicMerge:
metadata:
annotations:
networking.policy: "default-deny"
Service Load Balancer Configuration
Automatically configure LoadBalancer services:
apiVersion: policies.kyverno.io/v1
kind: MutatingPolicy
metadata:
name: configure-loadbalancer
annotations:
nyl.niklasrosenstein.github.com/apply-policy-scope: Global
spec:
matchConstraints:
resourceRules:
- apiGroups: ['']
apiVersions: ['v1']
operations: ['CREATE']
resources: ['services']
mutations:
- patchType: ApplyConfiguration
applyConfiguration:
expression: >-
object.spec.type == 'LoadBalancer'
? Object{
spec: Object.spec{
loadBalancerClass: "ngrok",
allocateLoadBalancerNodePorts: false
}
}
: object
Unannotated Policies
Policies without the scope annotation are treated as normal Kubernetes resources and included in the output. This allows you to:
- Deploy policies to the cluster along with your applications
- Manage policies as part of your GitOps workflow
- Use Nyl for some policies (with annotations) and deploy others normally
# This policy will be included in output but NOT applied by Nyl
apiVersion: policies.kyverno.io/v1
kind: MutatingPolicy
metadata:
name: cluster-policy # No scope annotation
spec:
matchConstraints:
resourceRules:
- apiGroups: ['apps']
apiVersions: ['v1']
operations: ['CREATE']
resources: ['deployments']
mutations:
- patchType: ApplyConfiguration
applyConfiguration:
expression: "object"
Policy Processing
Processing Order
- Policy Detection: Nyl scans all manifests for Kyverno policy CRDs
- Scope Extraction: Policies with the scope annotation are extracted and grouped by scope
- Policy Application: Global policies are applied to all non-policy resources
- Output: Mutated resources are output; annotated policy resources are excluded
Policy Exclusion
Policy resources themselves are automatically excluded from mutation by other policies. This prevents circular mutations and ensures policies remain unchanged.
Validation Failures
If a validation policy fails, the nyl render command will fail with an error:
$ nyl render
ERROR: Configuration error: Kyverno apply failed with exit code Some(1):
...
Validation error: ConfigMap must have 'environment' label
CEL Expressions
Kyverno policies use Common Expression Language (CEL) for mutations and validations.
Common Patterns
Check if field exists:
has(object.metadata.labels)
Check if key exists in map:
'environment' in object.metadata.labels
Conditional mutation:
object.spec.type == 'LoadBalancer'
? Object{spec: Object.spec{loadBalancerClass: "ngrok"}}
: object
Add or merge labels:
Object{
metadata: Object.metadata{
labels: Object.metadata.labels{
newLabel: "value"
}
}
}
Set deeply nested field:
Object{
spec: Object.spec{
template: Object.spec.template{
spec: Object.spec.template.spec{
securityContext: Object{runAsNonRoot: true}
}
}
}
}
Requirements
- Kyverno CLI: Must be installed and available in PATH
# Install on Linux curl -LO https://github.com/kyverno/kyverno/releases/download/v1.17.0/kyverno-cli_v1.17.0_linux_x86_64.tar.gz tar -xzf kyverno-cli_v1.17.0_linux_x86_64.tar.gz sudo mv kyverno /usr/local/bin/ # Install on macOS brew install kyverno # Verify installation kyverno version
Best Practices
- Use Global scope sparingly: Global policies affect all resources and can have wide-reaching effects
- Test policies locally: Use
nyl renderto verify policy behavior before deployment - Provide clear error messages: Include descriptive messages in validation policies
- Document policy intent: Use metadata annotations to document why policies exist
- Prefer immutable labels: Add labels rather than modifying existing ones
- Version your policies: Include version information in policy names or labels
Troubleshooting
Policy Not Applied
Issue: Policy appears in output but isn’t being applied
Solution: Check that the policy has the scope annotation:
metadata:
annotations:
nyl.niklasrosenstein.github.com/apply-policy-scope: Global
Kyverno CLI Not Found
Issue: Kyverno CLI is not installed but Kyverno policies were found
Solution: Install the Kyverno CLI (see Requirements section above)
Validation Policy Passes But Shouldn’t
Issue: Resources that should fail validation are passing
Solution: Verify the CEL expression logic and matchConstraints are correct. Test with kyverno apply directly:
kyverno apply policy.yaml --resource resource.yaml
Multiple Policies Conflict
Issue: Multiple policies are mutating the same field differently
Solution: Ensure policies are orthogonal (affect different fields) or use proper scoping to control application order
See Also
Schemas
nyl.toml JSON Schema
Generate from CLI:
nyl generate schema config
Committed schema used in published docs:
Renovate Integration
Nyl ships a reusable Renovate preset for Helm chart version bumps in HelmChart, Component shortcuts, and project.aliases.
Use The Shared Preset
In your project’s renovate.json:
{
"extends": [
"config:recommended",
"github>NiklasRosenstein/nyl//.github/renovate/nyl-helm-components.json5"
]
}
What It Matches
The preset adds regex managers for:
HelmChartresources (apiVersion: nyl.niklasrosenstein.github.com/v1)spec.chart.repository+spec.chart.name+spec.chart.version
Componentshortcut resources (apiVersion: components.nyl.niklasrosenstein.github.com/v1)kind: https://...#<chart>@<version>kind: oci://...@<version>kind: git+...#<path>@<ref>
nyl.tomlalias targets in[project.aliases]"myapi/v1/Kind" = "https://...#<chart>@<version>""myapi/v1/Kind" = "oci://...@<version>""myapi/v1/Kind" = "git+...#<path>@<ref>"
Datasource Mapping
https://...shortcuts and Helm repositories -> Renovatehelmdatasourceoci://...shortcuts and repositories -> Renovatedockerdatasourcegit+...shortcuts and repositories -> Renovategit-tagsdatasource
Notes And Limitations
- Local component kinds (for example
example/v1/Nginx) are intentionally ignored. - Matches expect canonical field names (
repository,name,version) inHelmChartspecs. git+...@<ref>updates depend on available Git tags for the referenced repository.
Troubleshooting
- If a dependency is not detected, first verify it includes an explicit
@<version>(orspec.chart.versionforHelmChart). - Run
renovate-config-validatoragainst your final config to verify schema and templates.
Validate Matcher Coverage
Nyl includes fixture-based matcher tests for the shared preset:
mise run test-renovate-matchers
The test runs the official renovate/renovate Docker image and validates extracted dependencies from Renovate’s own logs.
Docker daemon access is required.
Fixtures are in integrationtests/fixtures/renovate-matchers/:
positive/: inputs that must matchnegative/: inputs that must not matchexpected.json: expected extracted dependencies (datasource,depName,currentValue) per fixture file
The validation is extraction-focused: it asserts matching behavior and extracted dependency identity, and does not require successful network datasource lookups.
When changing .github/renovate/nyl-helm-components.json5, update fixtures and expected.json in the same change to keep matcher behavior explicit.