GitOps with ArgoCD¶
Naftiko Skipper is designed to work with ArgoCD. The operator manages the capability lifecycle inside Kubernetes — ArgoCD manages what gets deployed from Git.
How It Works¶
Developer pushes capability manifests to Git
│
▼
ArgoCD detects change and syncs to cluster
│
▼
Naftiko Skipper reconciles the Capability CR
│
▼
Running capability with full observability
Users never run kubectl apply — they just push to Git.
Platform Setup (done once by the platform team)¶
The skipper repo ships three ArgoCD Applications applied in sync-wave order:
| Wave | Application | Source |
|---|---|---|
| -1 | naftiko-crds |
config/crds/manifests/ |
| 0 | naftiko-defaults |
config/defaults/manifests/ |
| 1 | naftiko-skipper |
helm/naftiko-skipper/ |
Install ArgoCD¶
kubectl create namespace argocd
kubectl apply -n argocd \
-f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml \
--server-side --force-conflicts
kubectl rollout status deployment/argocd-server \
-n argocd --timeout=120s
Expose the UI¶
Get the admin password first:
kubectl get secret argocd-initial-admin-secret -n argocd \
-o jsonpath="{.data.password}" | base64 -d ; echo
Choose the method that matches your cluster:
Any cluster — port-forward (simplest):
minikube:
kubectl patch svc argocd-server -n argocd \
--type='json' \
-p='[{"op":"replace","path":"/spec/type","value":"NodePort"},
{"op":"add","path":"/spec/ports/0/nodePort","value":30080}]'
MINIKUBE_IP=$(minikube ip)
docker run -d --name argocd-bridge --restart=always \
-p 30080:30080 --network minikube \
alpine/socat TCP-LISTEN:30080,fork,reuseaddr TCP:${MINIKUBE_IP}:30080
# → open http://localhost:30080
kind:
kubectl patch svc argocd-server -n argocd \
--type='json' \
-p='[{"op":"replace","path":"/spec/type","value":"NodePort"},
{"op":"add","path":"/spec/ports/0/nodePort","value":30080}]'
NODE_IP=$(kubectl get nodes \
-o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
NETWORK=$(docker network ls | grep kind | awk '{print $2}')
docker run -d --name argocd-bridge --restart=always \
-p 30080:30080 --network ${NETWORK} \
alpine/socat TCP-LISTEN:30080,fork,reuseaddr TCP:${NODE_IP}:30080
# → open http://localhost:30080
Cloud cluster (EKS, GKE, AKS) — LoadBalancer:
kubectl patch svc argocd-server -n argocd \
--type='json' \
-p='[{"op":"replace","path":"/spec/type","value":"LoadBalancer"}]'
kubectl get svc argocd-server -n argocd \
-o jsonpath='{.status.loadBalancer.ingress[0].hostname}'
# → open http://<hostname>
Login with admin / password from above.
Install the CLI and login¶
# macOS
brew install argocd
# Linux
curl -sSL -o argocd-linux-amd64 \
https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
# Login
ARGOCD_PASSWORD=$(kubectl get secret argocd-initial-admin-secret \
-n argocd -o jsonpath="{.data.password}" | base64 -d)
argocd login localhost:YOUR_PORT \
--username admin \
--password $ARGOCD_PASSWORD \
--insecure
Apply the platform applications¶
# Wave -1: CRDs
kubectl apply -f \
https://raw.githubusercontent.com/naftiko/fleet/main/skipper/argocd/crds-application.yaml
kubectl wait --for=condition=Established \
crd/capabilities.naftiko.io \
crd/capabilityclasses.naftiko.io \
--timeout=60s
# Wave 0: CapabilityClass defaults (standard, premium, dev)
kubectl apply -f \
https://raw.githubusercontent.com/naftiko/fleet/main/skipper/argocd/defaults-application.yaml
# Wave 1: Operator via Helm
kubectl apply -f \
https://raw.githubusercontent.com/naftiko/fleet/main/skipper/argocd/operator-application.yaml
kubectl rollout status deployment/naftiko-skipper \
-n naftiko-system --timeout=90s
Verify all three are Synced:
kubectl get applications -n argocd
# NAME SYNC-STATUS HEALTH
# naftiko-crds Synced Healthy
# naftiko-defaults Synced Healthy
# naftiko-skipper Synced Healthy
User Setup (done once per capabilities repository)¶
Each team maintains their own Git repository of capabilities. One
ApplicationSet automatically creates one ArgoCD Application per
capability directory.
1. Connect your repo¶
2. Apply the ApplicationSet¶
curl -sO https://raw.githubusercontent.com/naftiko/fleet/main/skipper/capabilities/applicationset.template.yaml
sed \
-e 's|MY_CAPABILITIES|my-capabilities|g' \
-e 's|REPO_URL|https://github.com/USER/my-capabilities.git|g' \
applicationset.template.yaml | kubectl apply -f -
ArgoCD now watches the repo and creates one Application per directory
under capabilities/. This command is run once — all future
capabilities are deployed via Git push.
Capabilities Repository Structure¶
my-capabilities/
└── capabilities/
├── hello-world/
│ ├── configmap.yaml ← ikanos spec as a Kubernetes ConfigMap
│ └── capability.yaml ← Capability CR using specRef
└── shipyard/
├── configmap.yaml
├── capability.yaml
├── bind-secret.yaml ← Kubernetes Secret for binds
├── import-registry.yaml ← ConfigMap for registry import
└── import-legacy.yaml ← ConfigMap for legacy import
configmap.yaml¶
apiVersion: v1
kind: ConfigMap
metadata:
name: hello-world-spec
namespace: default
data:
capability.yaml: |
ikanos: "1.0.0-alpha4"
info:
display: Hello World
labels:
naftiko.io/tier: standard
capability:
exposes:
- type: rest
address: "0.0.0.0"
port: 3001
namespace: tutorial
resources:
hello:
path: /hello
operations:
get-hello:
method: GET
outputParameters:
- name: message
type: string
value: "Hello, World!"
- type: control
address: "0.0.0.0"
port: 9090
observability:
enabled: true
metrics:
local:
enabled: true
capability.yaml¶
apiVersion: naftiko.io/v1alpha3
kind: Capability
metadata:
name: hello-world
namespace: default
labels:
naftiko.io/tier: standard
annotations:
argocd.argoproj.io/sync-wave: "1" # apply after ConfigMap (wave 0)
spec:
specRef:
configMap: hello-world-spec
Deploying a Capability¶
mkdir -p capabilities/my-new-capability
# create configmap.yaml and capability.yaml
git add capabilities/my-new-capability/
git commit -m "feat: add my-new-capability"
git push
ArgoCD detects the new directory → creates cap-my-new-capability →
Skipper reconciles the Capability CR → capability is running in minutes.
Updating a Capability¶
# Edit the spec in configmap.yaml
nano capabilities/hello-world/configmap.yaml
git add capabilities/hello-world/configmap.yaml
git commit -m "fix: update hello-world spec"
git push
ArgoCD syncs the updated ConfigMap. Skipper detects the drift and reconciles the Deployment with the new spec.
Removing a Capability¶
ArgoCD prunes the Capability CR and its ConfigMap. Skipper's
OwnerReference cascade-deletes the Deployment, Service, and
ServiceMonitor automatically.
Sync Waves — Why Order Matters¶
ArgoCD sync waves ensure resources are applied in dependency order:
Wave -1 CRDs installed → Kubernetes learns Capability and CapabilityClass types
Wave 0 CapabilityClasses → standard / premium / dev tiers are available
Wave 1 Operator running → Skipper watches for Capability CRs
Within a capability directory, use sync-wave annotations to ensure secrets and ConfigMaps exist before the Capability CR is applied:
# bind-secret.yaml
metadata:
annotations:
argocd.argoproj.io/sync-wave: "0"
# configmap.yaml
metadata:
annotations:
argocd.argoproj.io/sync-wave: "0"
# capability.yaml
metadata:
annotations:
argocd.argoproj.io/sync-wave: "1"
Manual Reconcile¶
To force an immediate reconcile without changing the spec:
Useful after updating operator env vars (e.g. OTEL endpoint) or after restoring a deleted child resource.