Loading...
Loading...
GitOps continuous delivery toolkit for Kubernetes with Flux CD. Use for declarative deployments, Helm chart automation, Kustomize overlays, image update automation, multi-tenancy, and Git-based continuous delivery.
npx skill4agent add cosmix/claude-loom loom-fluxcd
# Install Flux CLI
curl -s https://fluxcd.io/install.sh | sudo bash
# Or using Homebrew
brew install fluxcd/tap/flux
# Verify installation
flux --version# Export GitHub personal access token
export GITHUB_TOKEN=<your-token>
# Bootstrap Flux
flux bootstrap github \
--owner=<github-username> \
--repository=<repo-name> \
--branch=main \
--path=clusters/production \
--personal \
--components-extra=image-reflector-controller,image-automation-controllerexport GITLAB_TOKEN=<your-token>
flux bootstrap gitlab \
--owner=<gitlab-group> \
--repository=<repo-name> \
--branch=main \
--path=clusters/production \
--personal# Validate all Flux resources
flux check
# Check specific resources
kubectl apply --dry-run=server -f clusters/production/├── clusters/
│ ├── production/
│ │ ├── flux-system/ # Flux components (managed by bootstrap)
│ │ ├── infrastructure.yaml # Infrastructure sources & kustomizations
│ │ └── apps.yaml # Application sources & kustomizations
│ └── staging/
│ ├── flux-system/
│ ├── infrastructure.yaml
│ └── apps.yaml
├── infrastructure/
│ ├── base/ # Base infrastructure
│ │ ├── ingress-nginx/
│ │ ├── cert-manager/
│ │ └── sealed-secrets/
│ └── overlays/
│ ├── production/
│ └── staging/
└── apps/
├── base/
│ ├── app1/
│ └── app2/
└── overlays/
├── production/
└── staging/├── clusters/
│ └── production/
│ ├── flux-system/
│ ├── tenants/
│ │ ├── team-a.yaml # Team A namespace and RBAC
│ │ └── team-b.yaml # Team B namespace and RBAC
│ └── infrastructure.yaml
├── tenants/
│ ├── base/
│ │ ├── team-a/
│ │ │ ├── namespace.yaml
│ │ │ ├── rbac.yaml
│ │ │ └── sync.yaml # GitRepository + Kustomization for team
│ │ └── team-b/
│ │ ├── namespace.yaml
│ │ ├── rbac.yaml
│ │ └── sync.yaml
│ └── overlays/
│ └── production/
└── teams/ # Separate repos or paths for each team
├── team-a-repo/
└── team-b-repo/apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: flux-system
namespace: flux-system
spec:
interval: 1m0s
ref:
branch: main
url: https://github.com/org/repo
secretRef:
name: flux-systemapiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: apps
namespace: flux-system
spec:
interval: 5m0s
ref:
branch: main
url: https://github.com/org/apps-repo
ignore: |
# Exclude all
/*
# Include specific paths
!/apps/production/apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: infrastructure
namespace: flux-system
spec:
interval: 10m0s
sourceRef:
kind: GitRepository
name: flux-system
path: ./infrastructure/production
prune: true
wait: true
timeout: 5m0sapiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
interval: 10m0s
dependsOn:
- name: infrastructure
sourceRef:
kind: GitRepository
name: flux-system
path: ./apps/production
prune: true
wait: true
timeout: 5m0s
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: app-name
namespace: app-namespace
postBuild:
substitute:
cluster_name: production
domain: example.com
substituteFrom:
- kind: ConfigMap
name: cluster-varsapiVersion: v1
kind: ConfigMap
metadata:
name: cluster-vars
namespace: flux-system
data:
cluster_name: production
cluster_region: us-east-1
domain: example.comapiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: default
data:
cluster: ${cluster_name}
region: ${cluster_region}
url: https://app.${domain}# clusters/production/tenants/team-a.yaml
apiVersion: v1
kind: Namespace
metadata:
name: team-a
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: team-a-reconciler
namespace: team-a
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-a-reconciler
namespace: team-a
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: team-a-reconciler
namespace: team-a
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: team-a-repo
namespace: team-a
spec:
interval: 1m
url: https://github.com/org/team-a-repo
ref:
branch: main
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: team-a-apps
namespace: team-a
spec:
interval: 10m
serviceAccountName: team-a-reconciler
sourceRef:
kind: GitRepository
name: team-a-repo
path: ./apps
prune: true
validation: clientapiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: team-a-reconciler
namespace: team-a
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-a-reconciler
namespace: team-a
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: team-a-reconciler
subjects:
- kind: ServiceAccount
name: team-a-reconciler
namespace: team-aapiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: team-a-apps
namespace: team-a
spec:
interval: 10m
dependsOn:
- name: shared-ingress
namespace: flux-system
- name: shared-monitoring
namespace: flux-system
sourceRef:
kind: GitRepository
name: team-a-repo
path: ./apps
prune: trueapiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
name: bitnami
namespace: flux-system
spec:
interval: 1h0s
url: https://charts.bitnami.com/bitnamiapiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
name: private-charts
namespace: flux-system
spec:
interval: 1h0s
url: https://charts.example.com
secretRef:
name: helm-charts-auth
---
apiVersion: v1
kind: Secret
metadata:
name: helm-charts-auth
namespace: flux-system
type: Opaque
stringData:
username: user
password: passapiVersion: helm.toolkit.fluxcd.io/v2beta2
kind: HelmRelease
metadata:
name: nginx-ingress
namespace: ingress-nginx
spec:
interval: 10m0s
chart:
spec:
chart: ingress-nginx
version: "4.8.x"
sourceRef:
kind: HelmRepository
name: ingress-nginx
namespace: flux-system
interval: 1h0s
values:
controller:
service:
type: LoadBalancerapiVersion: helm.toolkit.fluxcd.io/v2beta2
kind: HelmRelease
metadata:
name: my-app
namespace: apps
spec:
interval: 10m0s
chart:
spec:
chart: my-app
version: "1.0.x"
sourceRef:
kind: HelmRepository
name: my-charts
namespace: flux-system
values:
replicas: 2
valuesFrom:
- kind: ConfigMap
name: app-config
valuesKey: values.yaml
- kind: Secret
name: app-secrets
valuesKey: secrets.yamlapiVersion: helm.toolkit.fluxcd.io/v2beta2
kind: HelmRelease
metadata:
name: my-app
namespace: apps
spec:
interval: 10m0s
chart:
spec:
chart: my-app
version: "1.0.x"
sourceRef:
kind: HelmRepository
name: my-charts
namespace: flux-system
install:
remediation:
retries: 3
upgrade:
remediation:
retries: 3
remediateLastFailure: true
cleanupOnFail: true
test:
enable: true
rollback:
cleanupOnFail: true
recreate: true
values:
image:
tag: v1.0.0apiVersion: helm.toolkit.fluxcd.io/v2beta2
kind: HelmRelease
metadata:
name: my-app
namespace: apps
spec:
interval: 10m0s
dependsOn:
- name: cert-manager
namespace: cert-manager
- name: nginx-ingress
namespace: ingress-nginx
chart:
spec:
chart: my-app
version: "1.0.x"
sourceRef:
kind: HelmRepository
name: my-charts
namespace: flux-system
values:
ingress:
enabled: true
className: nginx# Install SOPS
brew install sops
# Install Age
brew install age
# Generate Age key
age-keygen -o age.agekey
# Get public key for .sops.yaml
age-keygen -y age.agekey.sops.yamlcreation_rules:
- path_regex: .*/production/.*\.yaml
encrypted_regex: ^(data|stringData)$
age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
- path_regex: .*/staging/.*\.yaml
encrypted_regex: ^(data|stringData)$
age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p# Create secret manifest
cat <<EOF > secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
namespace: apps
stringData:
username: admin
password: supersecret
EOF
# Encrypt with SOPS
sops --encrypt --in-place secret.yaml
# Decrypt for viewing
sops --decrypt secret.yamlcat age.agekey | kubectl create secret generic sops-age \
--namespace=flux-system \
--from-file=age.agekey=/dev/stdinapiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
interval: 10m0s
sourceRef:
kind: GitRepository
name: flux-system
path: ./apps/production
prune: true
decryption:
provider: sops
secretRef:
name: sops-agecreation_rules:
- path_regex: .*/production/.*\.yaml
encrypted_regex: ^(data|stringData)$
age: >-
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p,
age1zvkyg2lqzraa2lnjvqej32nkuu0ues2s82hzrye869xeexvn73equnujwj,
age1penhr3v0pklzv6lqrvt3zyqhfvqffkjn5j2qhzc8xr7q8vpfck4q7n8k3fContainer Registry
|
| (scan for tags)
v
ImageRepository
|
| (filter & select)
v
ImagePolicy
|
| (update manifests)
v
ImageUpdateAutomation
|
| (commit to Git)
v
GitRepository
|
| (reconcile)
v
Kustomization
|
v
Kubernetes ClusterapiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: my-app
namespace: flux-system
spec:
image: ghcr.io/org/my-app
interval: 1m0sapiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: my-app
namespace: flux-system
spec:
image: registry.example.com/org/my-app
interval: 1m0s
secretRef:
name: registry-credentials
---
apiVersion: v1
kind: Secret
metadata:
name: registry-credentials
namespace: flux-system
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: <base64-encoded-docker-config>apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: my-app
namespace: flux-system
spec:
imageRepositoryRef:
name: my-app
policy:
semver:
range: 1.0.xapiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: my-app-develop
namespace: flux-system
spec:
imageRepositoryRef:
name: my-app
policy:
alphabetical:
order: asc
filterTags:
pattern: "^develop-[a-f0-9]+-(?P<ts>[0-9]+)"
extract: "$ts"apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: my-app-build
namespace: flux-system
spec:
imageRepositoryRef:
name: my-app
policy:
numerical:
order: asc
filterTags:
pattern: "^build-(?P<num>[0-9]+)"
extract: "$num"apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
name: my-app
namespace: flux-system
spec:
interval: 1m0s
sourceRef:
kind: GitRepository
name: flux-system
git:
checkout:
ref:
branch: main
commit:
author:
email: fluxcdbot@users.noreply.github.com
name: fluxcdbot
messageTemplate: |
Automated image update
Automation name: {{ .AutomationObject }}
Files:
{{ range $filename, $_ := .Updated.Files -}}
- {{ $filename }}
{{ end -}}
Objects:
{{ range $resource, $_ := .Updated.Objects -}}
- {{ $resource.Kind }} {{ $resource.Name }}
{{ end -}}
Images:
{{ range .Updated.Images -}}
- {{.}}
{{ end -}}
update:
path: ./apps/production
strategy: SettersapiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: apps
spec:
template:
spec:
containers:
- name: app
image: ghcr.io/org/my-app:1.0.0 # {"$imagepolicy": "flux-system:my-app"}1.0.x>=1.0.0^develop-.*apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
name: my-app
namespace: flux-system
spec:
interval: 1m0s
sourceRef:
kind: GitRepository
name: flux-system
git:
checkout:
ref:
branch: main
push:
branch: image-updates
commit:
author:
email: fluxcdbot@users.noreply.github.com
name: fluxcdbot
messageTemplate: |
Automated image update by Flux
[ci skip]
update:
path: ./apps/production
strategy: SettersapiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Provider
metadata:
name: slack
namespace: flux-system
spec:
type: slack
channel: flux-notifications
secretRef:
name: slack-webhook-url
---
apiVersion: v1
kind: Secret
metadata:
name: slack-webhook-url
namespace: flux-system
stringData:
address: https://hooks.slack.com/services/YOUR/WEBHOOK/URLapiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
name: kustomization-failures
namespace: flux-system
spec:
providerRef:
name: slack
eventSeverity: error
eventSources:
- kind: Kustomization
name: "*"
exclusionList:
- ".*health check failed.*"apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
name: helm-releases
namespace: flux-system
spec:
providerRef:
name: slack
eventSeverity: info
eventSources:
- kind: HelmRelease
name: "*"
namespace: "*"
summary: "Helm Release {{ .InvolvedObject.name }} in {{ .InvolvedObject.namespace }}"apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Provider
metadata:
name: msteams
namespace: flux-system
spec:
type: msteams
secretRef:
name: msteams-webhook-url
---
apiVersion: v1
kind: Secret
metadata:
name: msteams-webhook-url
namespace: flux-system
stringData:
address: https://outlook.office.com/webhook/YOUR/WEBHOOK/URLapiVersion: notification.toolkit.fluxcd.io/v1
kind: Receiver
metadata:
name: github-receiver
namespace: flux-system
spec:
type: github
events:
- "ping"
- "push"
secretRef:
name: github-webhook-token
resources:
- kind: GitRepository
name: flux-system
---
apiVersion: v1
kind: Secret
metadata:
name: github-webhook-token
namespace: flux-system
type: Opaque
stringData:
token: <webhook-secret>fleet-infra/
├── clusters/
│ ├── production/
│ │ ├── flux-system/
│ │ └── cluster-config.yaml
│ ├── staging/
│ │ ├── flux-system/
│ │ └── cluster-config.yaml
│ └── development/
│ ├── flux-system/
│ └── cluster-config.yaml
├── infrastructure/
│ ├── base/
│ └── overlays/
│ ├── production/
│ ├── staging/
│ └── development/
└── apps/
├── base/
└── overlays/
├── production/
├── staging/
└── development/clusters/production/cluster-config.yamlapiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: infrastructure
namespace: flux-system
spec:
interval: 10m0s
sourceRef:
kind: GitRepository
name: flux-system
path: ./infrastructure/overlays/production
prune: true
wait: true
postBuild:
substitute:
cluster_name: production
cluster_region: us-east-1
replicas: "3"
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
interval: 10m0s
dependsOn:
- name: infrastructure
sourceRef:
kind: GitRepository
name: flux-system
path: ./apps/overlays/production
prune: true
postBuild:
substitute:
cluster_name: production
domain: prod.example.comapiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: cluster-staging
namespace: flux-system
spec:
interval: 10m0s
sourceRef:
kind: GitRepository
name: flux-system
path: ./clusters/staging
prune: true
kubeConfig:
secretRef:
name: staging-kubeconfig
---
apiVersion: v1
kind: Secret
metadata:
name: staging-kubeconfig
namespace: flux-system
type: Opaque
data:
value: <base64-encoded-kubeconfig># Base infrastructure
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: crds
namespace: flux-system
spec:
interval: 1h
sourceRef:
kind: GitRepository
name: flux-system
path: ./infrastructure/crds
prune: false # Never prune CRDs automatically
---
# Depends on CRDs
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: cert-manager
namespace: flux-system
spec:
interval: 10m
dependsOn:
- name: crds
sourceRef:
kind: GitRepository
name: flux-system
path: ./infrastructure/cert-manager
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: cert-manager
namespace: cert-manager
---
# Depends on cert-manager
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: ingress-nginx
namespace: flux-system
spec:
interval: 10m
dependsOn:
- name: cert-manager
sourceRef:
kind: GitRepository
name: flux-system
path: ./infrastructure/ingress-nginxapiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: database
namespace: flux-system
spec:
interval: 10m
sourceRef:
kind: GitRepository
name: flux-system
path: ./apps/database
healthChecks:
- apiVersion: apps/v1
kind: StatefulSet
name: postgresql
namespace: database
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: backend
namespace: flux-system
spec:
interval: 5m
dependsOn:
- name: database
sourceRef:
kind: GitRepository
name: flux-system
path: ./apps/backend
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: frontend
namespace: flux-system
spec:
interval: 5m
dependsOn:
- name: backend
sourceRef:
kind: GitRepository
name: flux-system
path: ./apps/frontendprune: trueprune: falsespec:
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: critical-app
namespace: apps
- apiVersion: v1
kind: Service
name: critical-service
namespace: apps# Suspend a Kustomization
flux suspend kustomization apps
# Resume reconciliation
flux resume kustomization apps# Reconcile a specific Kustomization
flux reconcile kustomization apps --with-source
# Reconcile a HelmRelease
flux reconcile helmrelease my-app -n apps# Check Flux components status
flux check
# Get all Flux resources
flux get all
# Get specific resource with detailed info
flux get kustomization infrastructure
# View logs
flux logs --level=error --all-namespaces
# Export current cluster state
flux export source git flux-system
flux export kustomization --all
# Backup Flux configuration
flux export source git --all > sources.yaml
flux export kustomization --all > kustomizations.yaml
flux export helmrelease --all > helmreleases.yaml
# Restore from backup
kubectl apply -f sources.yaml
kubectl apply -f kustomizations.yaml
kubectl apply -f helmreleases.yamlapiVersion: helm.toolkit.fluxcd.io/v2beta2
kind: HelmRelease
metadata:
name: flagger
namespace: flagger-system
spec:
interval: 10m
chart:
spec:
chart: flagger
version: "1.x"
sourceRef:
kind: HelmRepository
name: flagger
namespace: flux-system
---
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: my-app
namespace: apps
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
service:
port: 80
analysis:
interval: 1m
threshold: 5
maxWeight: 50
stepWeight: 10
metrics:
- name: request-success-rate
thresholdRange:
min: 99
interval: 1mapiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: external-secrets
namespace: flux-system
spec:
interval: 10m
sourceRef:
kind: GitRepository
name: flux-system
path: ./infrastructure/external-secrets
prune: true
---
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secretsmanager
namespace: apps
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
namespace: apps
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secretsmanager
kind: SecretStore
target:
name: app-secrets
creationPolicy: Owner
data:
- secretKey: db-password
remoteRef:
key: prod/app/database
property: password# Check Kustomization status
flux get kustomization infrastructure
# View detailed events
kubectl describe kustomization infrastructure -n flux-system
# Check logs
kubectl logs -n flux-system deploy/kustomize-controller# Get HelmRelease status
flux get helmrelease my-app -n apps
# View Helm release history
helm history my-app -n apps
# Check Helm controller logs
kubectl logs -n flux-system deploy/helm-controller# Check ImageRepository status
flux get image repository my-app
# Check ImagePolicy status
flux get image policy my-app
# View image automation logs
kubectl logs -n flux-system deploy/image-reflector-controller
kubectl logs -n flux-system deploy/image-automation-controller# Check GitRepository status
flux get source git flux-system
# View source controller logs
kubectl logs -n flux-system deploy/source-controller
# Reconcile manually
flux reconcile source git flux-system# Patch controller for debug logging
kubectl patch deployment kustomize-controller \
-n flux-system \
--type='json' \
-p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--log-level=debug"}]'spec:
interval: 1h # Increase for stable resources
retryInterval: 5m # Retry less frequently on errorsapiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: flux-system
namespace: flux-system
spec:
interval: 5m
ref:
branch: main
url: https://github.com/org/repo
ignore: |
# Reduce clone size
*.md
docs/
examples/flux install \
--components-extra=image-reflector-controller,image-automation-controller \
--reconcile-interval=1h \
--kustomize-concurrency=10 \
--helm-concurrency=10flux bootstrap