GitOps with ArgoCD: Declarative Deployments
Manual kubectl apply doesn't scale. Today I set up ArgoCD for GitOps - the cluster state now matches what's in Git, automatically.
Why GitOps?
The traditional deployment flow:
- Build image
- Push to registry
- SSH to server
- Run
kubectl apply - Hope nothing breaks
The GitOps flow:
- Build image
- Push to registry
- Update image tag in Git
- ArgoCD syncs automatically
Git becomes the single source of truth. Every change is auditable. Rollbacks are just git revert.
Installing ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yamlGet the initial admin password:
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -dExposing ArgoCD
I created an Ingress to access the ArgoCD UI:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argocd-server-ingress
namespace: argocd
annotations:
traefik.ingress.kubernetes.io/router.tls: "true"
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- argocd.el-jefe.me
secretName: argocd-tls
rules:
- host: argocd.el-jefe.me
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: argocd-server
port:
number: 443Application Configuration
Each application gets an ArgoCD Application manifest:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: bookmarked
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/maxjeffwell/devops-portfolio-manager
targetRevision: HEAD
path: helm-charts/bookmarked
helm:
valueFiles:
- values.yaml
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=trueKey settings:
automated.prune: Delete resources removed from Gitautomated.selfHeal: Revert manual cluster changestargetRevision: HEAD: Always sync to latest commit
Traefik Ingress Configuration
k3s comes with Traefik as the default ingress controller. I configured it to handle all my application subdomains:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: portfolio-apps-ingress
annotations:
traefik.ingress.kubernetes.io/router.tls: "true"
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- bookmarked-k8s.el-jefe.me
- code-talk-k8s.el-jefe.me
- educationelly-k8s.el-jefe.me
- intervalai-k8s.el-jefe.me
- firebook-k8s.el-jefe.me
secretName: portfolio-apps-tls
rules:
- host: bookmarked-k8s.el-jefe.me
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: bookmarked-client
port:
number: 80
# ... similar rules for other appsThe Deployment Flow Now
- GitHub Actions builds and pushes Docker image
- Workflow updates image tag in Helm values.yaml
- Git push triggers ArgoCD sync
- ArgoCD applies the changes to the cluster
- Traefik routes traffic to the new pods
The entire flow is visible in ArgoCD's UI:
Application: bookmarked
Status: Synced ✓
Health: Healthy ✓
Last Sync: 2 minutes agoHandling Multiple Apps
With 6 applications, I structured the ArgoCD apps using an App of Apps pattern:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: portfolio-apps
namespace: argocd
spec:
source:
repoURL: https://github.com/maxjeffwell/devops-portfolio-manager
path: argocd-apps
destination:
server: https://kubernetes.default.svc
namespace: argocdThe argocd-apps/ directory contains Application manifests for each portfolio app. Adding a new app is just creating a new YAML file.
Debugging Sync Issues
ArgoCD's diff view saved me hours of debugging. When a sync fails:
argocd app diff bookmarkedShows exactly what's different between Git and the cluster.
Common issues I hit:
- Immutable field changes - Can't change certain fields on existing resources
- Missing CRDs - External Secrets CRDs needed to be installed first
- Namespace mismatches - Helm release namespace vs. ArgoCD destination
Current Ingress Layout
| Subdomain | Application |
|---|---|
| argocd.el-jefe.me | ArgoCD UI |
| grafana.el-jefe.me | Grafana dashboards |
| bookmarked-k8s.el-jefe.me | Bookmarked app |
| code-talk-k8s.el-jefe.me | Code Talk app |
| educationelly-k8s.el-jefe.me | EducationELLy app |
| intervalai-k8s.el-jefe.me | IntervalAI app |
| firebook-k8s.el-jefe.me | FireBook app |
All with automatic TLS via cert-manager and Let's Encrypt.
What's Next
The GitOps foundation is solid. Next I'm planning to:
- Add Keel for automatic image updates
- Set up Velero for cluster backups
- Add a GPU node for AI workloads
Documenting the evolution of my homelab infrastructure.
