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-project.yaml # Nyl project configuration
├── nyl-profiles.yaml # Nyl profiles (environments)
└── nyl-secrets.yaml # Secrets configuration
Step 1: Create Configuration Files
nyl-project.yaml
{}
nyl-profiles.yaml
default:
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: 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"
release:
name: argocd
namespace: argocd
createNamespace: true
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
generate:
command: ["/bin/sh", "-c"]
args:
- nyl render .
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
spec:
chart:
repository: https://charts.bitnami.com/bitnami
name: nginx
version: "15.4.4"
release:
name: nginx
namespace: default
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
spec:
chart:
repository: https://charts.bitnami.com/bitnami
name: redis
version: "18.4.0"
release:
name: redis
namespace: default
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
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
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 . - 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: nyl.niklasrosenstein.github.com/v1
kind: HelmChart
metadata:
name: postgres
spec:
chart:
repository: https://charts.bitnami.com/bitnami
name: postgresql
version: "13.2.24"
release:
name: postgres
namespace: database
createNamespace: true
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