DEV Community

Cover image for GitOps with ArgoCD
Omar Ahmed
Omar Ahmed

Posted on • Edited on

GitOps with ArgoCD

gitopsWithArgoCD-Docs
gitops-argocd-Repo

1. What is GitOps?

GitOps is a modern approach to managing and deploying infrastructure and applications using Git as the single source of truth.
Instead of manually applying changes to servers or clusters, you declare the desired system state in Git, and an automated tool keeps your running systems in sync with that state.

Core Principles :

  • Declarative configuration: All infrastructure and application configs are stored as code in Git repositories (e.g., Kubernetes manifests, Helm charts, Terraform modules).
  • Version-controlled: Every change is done through Git commits, pull requests, and code reviews—giving you full history, auditability, and rollback capability.
  • Automated reconciliation: Specialized GitOps controllers (like Argo CD or Flux) continuously watch the Git repo and the live environment. If something drifts, they automatically sync it back to match the Git state. The GitOps operator ArgoCD also makes sure that the entire system is self-healing to reduce the risk of human errors. The operator continuously loops through three steps, observe, diff, and act. In the observe step, it checks the Git repository for any changes in the desired state. In the diff step, it compares the resources received from the previous step the observe step to the actual state of the cluster ( It performs a diff (comparison) between the desired state (Git) and the actual state (cluster), Any mismatch is called “drift” ). And in the Act step, it uses a reconciliation function and tries to match the actual state to the desired state ( If a drift is found, the operator runs its reconciliation logic, It applies the necessary changes to bring the cluster back to match Git, This ensures the system self-heals from manual or accidental changes ).

Desired State: Git , Actual State: Kubernetes Cluster

  • Continuous deployment: Merging to the main branch becomes the trigger for deployments, replacing manual kubectl apply or ad-hoc scripts.

Benefits :

  • Full audit trail of all infrastructure and app changes.
  • Rollback is as simple as reverting a Git commit.
  • Strong security (no direct access to production clusters needed - pull the desired state from Git and apply it in one or more environments or clusters)
  • Enables collaboration and consistent workflows across teams.

Push vs Pull based Deployments :

1. Push-based Deployment :
How it works :

  • The CI/CD system (like Jenkins) builds the code, creates container images, pushes them to a registry, and then directly applies manifests to the Kubernetes cluster using kubectl apply.
  • The CI/CD system has Read-Write (RW) access to the cluster.

Key points from the image :

  • ✅ Easy to deploy Helm charts.
  • ✅ Easy to inject container version updates via the build pipeline.
  • ✅ Simpler secret management from inside the pipeline.
  • ❌ Cluster config is embedded in the CI system (tightly coupled).
  • ❌ CI system holds RW access to the cluster (security risk).
  • ❌ Deployment approach is tied to the CD system (less flexible).

2. Pull-based Deployment (GitOps) :
How it works :

  • The CI system builds and pushes images to a registry.
  • The desired manifests are committed to a Git repository.
  • A GitOps operator (like Argo CD) inside the cluster pulls the manifests from Git and syncs them to the cluster, reconciling continuously.

Key points from the image :

  • ✅ No external user/client can modify the cluster (only GitOps operator can).
  • ✅ Can scan container registries for new versions.
  • ✅ Secrets can be managed via Git repo + Vault.
  • ✅ Not coupled to the CD pipeline — independent.
  • ✅ Supports multi-tenant setups.
  • ❌ Managing secrets for Helm deployments is harder.
  • ❌ Generic secret management is more complex.

2. ArgoCD Basics

What is Argo CD?
Argo CD (Argo Continuous Delivery) is a GitOps tool for Kubernetes that:

  • Runs inside your cluster as a controller.
  • Continuously monitors a Git repository that stores your Kubernetes manifests (YAML, Helm, Kustomize, etc.).
  • Automatically applies and syncs those manifests to the cluster.
  • Provides a web UI, CLI, and API to visualize and manage application deployments.

Installation :

helm repo add argo https://argoproj.github.io/argo-helm helm repo update kubectl create namespace argocd helm install argocd argo/argo-cd -n argocd # Optional: Specify custom values helm show values argo/argo-cd > values.yaml helm install argocd argo/argo-cd -n argocd -f values.yaml kubectl port-forward svc/argocd-server --address=0.0.0.0 -n argocd 8080:443 # Get the Initial Admin Password kubectl -n argocd get secret argocd-initial-admin-secret \ -o jsonpath="{.data.password}" | base64 -d; echo 
Enter fullscreen mode Exit fullscreen mode

Why use Argo CD?
Argo CD solves common deployment and operations problems by embracing GitOps principles:

Problem How Argo CD Helps
Manual kubectl apply steps Automates syncing from Git
Configuration drift (manual changes) Detects drift and self-heals by reverting to Git
Hard to audit who changed what Uses Git history as the single source of truth
CI pipelines need cluster credentials Removes this risk — cluster pulls from Git
Slow feedback on deployment status Real-time UI with health, sync status, and diffs

ArgoCD Concepts & Terminology :

Term Description
Application A group of Kubernetes resources as defined by a manifest.
An application is a Custom Resource Definition, which represents a deployed application instance in a cluster.
An application in ArgoCD is defined by two key pieces of information, the source Git repo where is the desired state of a kubernetes manifest and the destination for your Kubernetes resources where the resources should be deployed in a kubernetes.
Application source type The tool used to build the application. E.g., Helm, Kustomize, or Ksonnet.
Project Provides a logical grouping of applications, useful when Argo CD is used by multiple teams.
Target state The desired state of an application, as represented by files in a Git repository.
Live state The live state of that application. What pods, configmaps, secrets, etc. are created/deployed in a Kubernetes cluster.
Sync status Whether or not the live state matches the target state. Is the deployed application the same as Git says it should be?
Sync The process of making an application move to its target state (e.g., by applying changes to a Kubernetes cluster).
Sync operation status Whether or not a sync succeeded.
Refresh Compare the latest code in Git with the live state to figure out what is different.
Health The health of the application — is it running correctly? Can it serve requests?

What is an Argo CD Application?
An Application is the core deployment unit in Argo CD.
It represents a set of Kubernetes resources defined in a Git repository and tells Argo CD:

  • Where to get the manifests (Git repo + path + branch)
  • Where to deploy them (cluster + namespace)
  • How to manage them (sync policies like auto-sync, self-heal)

Creating an Application :
1.Using CLI

argocd app create color-app \ --repo https://github.com/sid/app-1.git \ --path team-a/color-app \ --dest-namespace color \ --dest-server https://kubernetes.default.svc 
Enter fullscreen mode Exit fullscreen mode

Explanation:
--repo: Git repository URL containing the manifests.
--path: Path inside the repo where the manifests are stored.
--dest-namespace: Target namespace in the cluster.
--dest-server: Target Kubernetes API server (usually the same cluster).

argocd login localhost:8080 --username admin --password <password> argocd app create solar-system-app \ --repo https://github.com/sidd-harth/gitops-argocd \ --path ./solar-system \ --dest-namespace solar-system \ --dest-server https://kubernetes.default.svc argocd app list argocd app sync solar-system-app argocd app get solar-system-app # check using kubectl with the namespace that argocd deployed in it kubectl get app -n gitops kubectl describe app -n gitops 
Enter fullscreen mode Exit fullscreen mode

2.Using a YAML manifest (color-app.yaml)

apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: color-app namespace: argocd spec: project: default source: repoURL: https://github.com/sid/app-1.git targetRevision: HEAD path: team-a/color destination: server: https://kubernetes.default.svc namespace: color syncPolicy: automated: selfHeal: true syncOptions: - CreateNamespace=true 
Enter fullscreen mode Exit fullscreen mode

Key fields:

  • metadata.name: Application name shown in Argo CD UI.
  • spec.source: Where manifests come from (repo, branch, path).
  • spec.destination: Which cluster and namespace to deploy to.
  • syncPolicy.automated: Enables auto-sync and self-healing.
  • syncOptions.CreateNamespace=true: Automatically create the namespace if it doesn’t exist.

3.using UI

  • Click “+ NEW APP”
  • Application Name: A unique name (e.g. color-app)
  • Project: Select default (unless you created custom projects)
  • Sync Policy:

    • Choose Manual or Automatic
    • Enable Self Heal and Prune if you want Argo CD to auto-fix drift and delete removed resources
  • Specify the Source (Git Repo)

    • Repository URL or Added it using settings => Repositories => Connect Repo => Via HTTP/HTTPS => Type: git => Name: Any Name => Project: default => Repository URL then check the Connection Status
    • Revision: HEAD (or branch/tag name like main)
    • Path: The path in the repo (./solar-system)
  • Specify the Destination (Cluster + Namespace)

    • Cluster URL:
    • Namespace: solar-system
      • Check “Create Namespace” if it does not exist
  • Review and Create

    • Click “Create”
  • Sync the Application

    • After creation, go to the app page
    • Click “Sync” (if using manual sync)
    • Argo CD will pull the manifests and deploy them to your cluster

ArgoCD Architecture :
Argo CD is deployed as a set of Kubernetes controllers and services inside your cluster.
It continuously pulls manifests from Git and applies them to the cluster, keeping everything in sync.

Component Role
API Server Provides the UI, CLI (argocd), and REST API. Handles RBAC and authentication.
Repository Server (Repo Server) Clones Git repositories and renders manifests (Helm, Kustomize, etc.).
Application Controller Core GitOps reconciliation engine: compares desired vs live state and performs sync actions.
Dex (Optional) Identity provider for SSO authentication (OIDC, GitHub, LDAP, etc.).
Redis (Optional) Used as a cache to speed up repository and application state operations.

Create ArgoCD Project :
When you install Argo CD, it automatically creates a built-in project called default :

  • Has no restrictions by default
  • Allows any Git repository as a source
  • Allows deployments to any cluster and any namespace
  • Allows all resource kinds (cluster-scoped and namespace-scoped)
❯❯❯ argocd proj list NAME DESCRIPTION DESTINATIONS SOURCES CLUSTER-RESOURCE-WHITELIST NAMESPACE-RESOURCE-BLACKLIST SIGNATURE-KEYS ORPHANED-RESOURCES default *,* * */* <none> <none> disabled 
Enter fullscreen mode Exit fullscreen mode
kubectl get appproj -n gitops # check project 
Enter fullscreen mode Exit fullscreen mode

we'll try to create a custom project which has some restrictions :
Creating your own AppProject lets you:

  • Restrict which repos can be used
  • Restrict which clusters/namespaces can be deployed to
  • Apply RBAC per project

Using UI :

  • Go to Settings → Projects
  • Click “+ NEW PROJECT”
  • Project Name and Description
  • Create
  • Source Repositories: only repos will be allowed.
  • Destinations (CLUSTER + NAMESPACE)

ClusterRole (in the rbac.authorization.k8s.io API group) is restricted.
Any Application assigned to this Project that tries to create, update, or delete a ClusterRole will be blocked by Argo CD.
The sync will fail with a permission/validation error, and the ClusterRole resource will not be applied.

Section What it means
Cluster Resource Allow List A whitelist of cluster-scoped resources (apply to the whole cluster, not tied to a namespace) that applications are allowed to create/update/delete. If empty → all are allowed by default.
Cluster Resource Deny List A blacklist of cluster-scoped resources that applications are forbidden from managing. In your case, ClusterRole is listed here — meaning apps in this project cannot create or modify ClusterRoles.
Namespace Resource Allow List A whitelist of namespace-scoped resources (like Deployments, ConfigMaps, Services, etc.) that applications are allowed to manage.
Namespace Resource Deny List A blacklist of namespace-scoped resources that are forbidden.


❯❯❯ argocd proj get test-project Name: test-project Description: for testing Destinations: https://kubernetes.default.svc,* Repositories: https://github.com/sidd-harth/gitops-argocd Scoped Repositories: <none> Allowed Cluster Resources: <none> Scoped Clusters: <none> Denied Namespaced Resources: <none> Signature keys: <none> Orphaned Resources: disabled 
Enter fullscreen mode Exit fullscreen mode
argocd proj get test-project -o yaml 
Enter fullscreen mode Exit fullscreen mode
metadata: creationTimestamp: "2025-09-13T18:25:20Z" generation: 5 managedFields: - apiVersion: argoproj.io/v1alpha1 fieldsType: FieldsV1 fieldsV1: f:spec: .: {} f:clusterResourceBlacklist: {} f:description: {} f:destinations: {} f:sourceRepos: {} f:status: {} manager: argocd-server operation: Update time: "2025-09-13T18:43:52Z" name: test-project namespace: gitops resourceVersion: "30359" uid: 829347ce-cb3d-46bb-8b1b-8bef6f5a9a56 spec: clusterResourceBlacklist: - group: '""' kind: ClusterRole description: for testing destinations: - name: in-cluster namespace: '*' server: https://kubernetes.default.svc sourceRepos: - https://github.com/sidd-harth/gitops-argocd status: {} 
Enter fullscreen mode Exit fullscreen mode

Applications in test-project can deploy anything from the specified Git repo to any namespace in the in-cluster cluster — except creating ClusterRole objects.


3. ArgoCD Intermediate

Reconciliation loop

1. timeout.reconciliation (TIMEOUT option) :
What it is:
A ConfigMap setting that defines the maximum duration of a single reconciliation loop.
A reconciliation loop is how often your ArgoCD application will synchronize from the Git repository :

  • In a generic ArgoCD configuration, the default timeout period is set to 3 minutes.
  • This value is configurable and is used within the ArgoCD repo server.
  • The ArgoCD repo server is responsible to retrieve the desired state from the Git repo server and it has a timeout option called as the application reconciliation timeout.
  • If we check the environment variable of ArgoCD repo server pod, it says that the key timeout reconciliation can be configured on an ArgoCD config map.
kubectl -n gitops describe pod argocd-repo-server-cd79f5cc4-lvvgp | grep -i "ARGOCD_RECONCILIATION_TIMEOUT:" -B1 kubectl get cm argocd-cm -n gitops -o yaml | grep -i timeout # kubectl patch command to update the timeout.reconciliation value in the argocd-cm kubectl -n gitops patch configmap argocd-cm --patch='{"data":{"timeout.reconciliation":"300s"}}' kubectl -n gitops rollout restart deploy/argocd-repo-server 
Enter fullscreen mode Exit fullscreen mode

The argocd-repo-server takes the config value from argocd-cm :

❯❯❯ kubectl -n gitops describe pod argocd-repo-server-cd79f5cc4-lvvgp | grep -i "ARGOCD_RECONCILIATION_TIMEOUT:" -B1 ARGOCD_REPO_SERVER_NAME: argocd-repo-server ARGOCD_RECONCILIATION_TIMEOUT: <set to the key 'timeout.reconciliation' of config map 'argocd-cm'> Optional: true 
Enter fullscreen mode Exit fullscreen mode
❯❯❯ k get cm argocd-cm -n gitops -o yaml | grep -i timeout.reconciliation timeout.reconciliation: 180s 
Enter fullscreen mode Exit fullscreen mode

2. Webhook (GIT WEBHOOK option) :
What it is:
An event trigger that tells Argo CD to immediately start a reconciliation loop when new commits are pushed.
Where configured:
In your Git hosting platform (GitHub/GitLab/Bitbucket etc.)
You add a webhook pointing to Argo CD’s API:

https://<argocd-server>/api/webhook 
Enter fullscreen mode Exit fullscreen mode

kubectl -n gitops rollout restart deploy/argocd-repo-server
Effect:
Instead of waiting for the default polling interval (3 min), Argo CD instantly sees new commits and starts reconciliation.

By default, the argocd-server component only serves HTTPS (TLS) on port 443/8080.
When your Gitea webhook points to http://.../api/webhook instead of https://..., Argo CD will reject it (because it expects TLS), won’t accept it without valid HTTPS.
The --insecure flag tells argocd-server: “Serve plain HTTP instead of HTTPS.”

kubectl edit -n gitops deployments.apps argocd-server 
Enter fullscreen mode Exit fullscreen mode
containers: - args: - /usr/local/bin/argocd-server - --insecure # add this line 
Enter fullscreen mode Exit fullscreen mode
❯❯❯ k get po -n gitops argocd-server-554fb76c44-ck57g ⎈ (kind-kind/solar-system) NAME READY STATUS RESTARTS AGE argocd-server-554fb76c44-ck57g 1/1 Running 0 8m35s 
Enter fullscreen mode Exit fullscreen mode
kubectl port-forward svc/argocd-server --address=0.0.0.0 -n gitops 8080:80 
Enter fullscreen mode Exit fullscreen mode

  • Create a New App in ArgoCD
  • Now, when you push a new commit, Argo will show an Out of Sync status for this App.

Application health

Status Meaning
🟢 Healthy All resources are 100% healthy
🔵 Progressing Resource is unhealthy, but could still be healthy given time
💗 Degraded Resource status indicates a failure or an inability to reach a healthy state
🟡 Missing Resource is not present in the cluster
🟣 Suspended Resource is suspended or paused. Typical example is a paused Deployment
Unknown Health assessment failed and actual health status is unknown

Sync Strategies

Feature Description
Manual or automatic sync If set to automatic, Argo CD will apply the changes then update or create new resources in the target Kubernetes cluster.
Auto-pruning of resources Auto-pruning feature describes what happens when files are deleted or removed from Git.
Self-Heal of cluster Self-heal defines what Argo CD does when you make kubectl edit changes directly to the cluster.

Declarative Setup - Mono Application

Mono-application = one Application object tracks exactly one app path.
Keep app config declarative in Git; Argo CD watches it and reconciles automatically (if automated enabled).


apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: geocentric-model-app namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io spec: project: default source: repoURL: http://165.22.209.118:3000/siddharth/gitops-argocd.git targetRevision: HEAD path: ./declarative/manifests/geocentric-model # this path contains deployment and svc destination: server: https://kubernetes.default.svc namespace: geocentric-model syncPolicy: syncOptions: - CreateNamespace=true automated: prune: true selfHeal: true 
Enter fullscreen mode Exit fullscreen mode
kubectl apply -f filename.yaml 
Enter fullscreen mode Exit fullscreen mode

App of Apps

What is App of Apps?

  • Mono-app = one Application → one set of manifests.
  • App of Apps = one parent/root Application points to a folder containing multiple Application YAMLs.
  • Argo CD applies the root app → which creates all the child apps.
  • You only need to manually create the root application — all other apps are bootstrapped from Git.

Example repo structure :

gitops-repo/ └─ root/ ├─ app-of-apps.yaml # the parent Argo CD Application ├─ apps/ │ ├─ frontend.yaml # child app definition │ ├─ backend.yaml # child app definition │ └─ database.yaml # child app definition ├─ frontend/ # actual manifests (Helm/Kustomize/YAML) │ ├─ deployment.yaml │ └─ service.yaml ├─ backend/ └─ database/ 
Enter fullscreen mode Exit fullscreen mode

Parent Application (app-of-apps.yaml) :

apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: root-app namespace: argocd spec: project: default source: repoURL: https://github.com/your-org/gitops-repo.git targetRevision: main path: root/apps # folder that holds child App YAMLs directory: recurse: true # include all files recursively destination: server: https://kubernetes.default.svc namespace: argocd syncPolicy: automated: selfHeal: true prune: true syncOptions: - CreateNamespace=true 
Enter fullscreen mode Exit fullscreen mode

One of the child applications (frontend.yaml) :

apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: frontend namespace: argocd spec: project: default source: repoURL: https://github.com/your-org/gitops-repo.git targetRevision: main path: root/frontend destination: server: https://kubernetes.default.svc namespace: frontend syncPolicy: automated: selfHeal: true prune: true syncOptions: - CreateNamespace=true 
Enter fullscreen mode Exit fullscreen mode

How it works:
1.Apply the root app:

kubectl apply -f root/app-of-apps.yaml -n argocd 
Enter fullscreen mode Exit fullscreen mode

2.Argo CD syncs the root app.
3.Root app creates the child Application objects from Git.
4.Each child app manages its own manifests as if it were a standalone app.

Deploy apps using HELM Chart

1. Deploy Using My Helm Chart :

❯❯❯ cd gitops-argocd/ ⎈ (kind-kind/solar-system) [I] [~/gitops-argocd] (22:34:45) main ❯❯❯ ls | grep config ⎈ (kind-kind/solar-system) 📂 declarative 📂 health-check 📂 helm-chart 📂 jenkins-demo 📄 LICENSE 📂 nginx-app 📂 sealed-secret 📂 solar-system 📂 vault-secrets [I] [~/gitops-argocd] (22:34:46) main ❯❯❯ cd helm-chart/ ⎈ (kind-kind/solar-system) [I] [~/g/helm-chart] (22:34:52) main ❯❯❯ cd templates/ ⎈ (kind-kind/solar-system) [I] [~/g/h/templates] (22:34:56) main ❯❯❯ pwd ⎈ (kind-kind/solar-system) /home/poseidon/gitops-argocd/helm-chart/templates [I] [~/g/h/templates] (22:34:58) main ❯❯❯ ls ⎈ (kind-kind/solar-system) 📄 _helpers.tpl 📄 configmap.yaml 📄 deployment.yaml 📄 NOTES.txt 📄 service.yaml [I] [~/g/h/templates] (22:35:00) main ❯❯❯ cat deployment.yaml ⎈ (kind-kind/solar-system) apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Release.Name }}-deploy labels: {{- include "random-shapes.labels" . | nindent 4 }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: {{- include "random-shapes.selectorLabels" . | nindent 6 }} template: metadata: {{- with .Values.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "random-shapes.selectorLabels" . | nindent 8 }} spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}" imagePullPolicy: {{ .Values.image.pullPolicy }} envFrom: - configMapRef: name: {{ .Release.Name }}-configmap [I] [~/g/h/templates] (22:35:04) main ❯❯❯ cat service.yaml ⎈ (kind-kind/solar-system) apiVersion: v1 kind: Service metadata: name: {{ .Release.Name }}-service labels: {{- include "random-shapes.labels" . | nindent 4 }} spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} targetPort: {{ .Values.service.targetPort }} protocol: TCP name: http selector: {{- include "random-shapes.selectorLabels" . | nindent 4 }} [I] [~/g/h/templates] (22:35:07) main ❯❯❯ cd .. ⎈ (kind-kind/solar-system) [I] [~/g/helm-chart] (22:35:12) main ❯❯❯ ls ⎈ (kind-kind/solar-system) 📄 Chart.yaml 📂 templates 📄 values.yaml [I] [~/g/helm-chart] (22:35:13) main ❯❯❯ cat values.yaml ⎈ (kind-kind/solar-system) replicaCount: 1 image: repository: siddharth67/php-random-shapes:v1 pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. tag: "" imagePullSecrets: [] nameOverride: "" fullnameOverride: "" service: type: ClusterIP port: 80 targetPort: 80 color: circle: black oval: black triangle: black rectangle: black square: black [I] [~/g/helm-chart] (22:35:17) main ❯❯❯ cat Chart.yaml ⎈ (kind-kind/solar-system) apiVersion: v2 name: random-shapes-chart description: A Helm chart for Random Shape App version: 1.0.0 
Enter fullscreen mode Exit fullscreen mode
argocd app create helm-random-shapes \ --repo https://github.com/sidd-harth/gitops-argocd \ --path helm-chart \ --helm-set replicaCount=2 \ --helm-set color.circle=pink \ --helm-set color.square=green \ --helm-set service.type=ClusterIP \ --dest-namespace default \ --dest-server https://kubernetes.default.svc 
Enter fullscreen mode Exit fullscreen mode
❯❯❯ argocd app create helm-random-shapes \ ⎈ (kind-kind/solar-system) --repo https://github.com/sidd-harth/gitops-argocd \ --path helm-chart \ --helm-set replicaCount=2 \ --helm-set color.circle=pink \ --helm-set color.square=green \ --helm-set service.type=ClusterIP \ --dest-namespace default \ --dest-server https://kubernetes.default.svc application 'helm-random-shapes' created [I] [~/gitops] (22:32:01) ❯❯❯ h ls ⎈ (kind-kind/solar-system) NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION [I] [~/gitops] (22:32:09) ❯❯❯ argocd app get helm-random-shapes ⎈ (kind-kind/solar-system) Name: gitops/helm-random-shapes Project: default Server: https://kubernetes.default.svc Namespace: default URL: https://argocd.example.com/applications/helm-random-shapes Source: - Repo: https://github.com/sidd-harth/gitops-argocd Target: Path: helm-chart SyncWindow: Sync Allowed Sync Policy: Manual Sync Status: OutOfSync from (05e2501) Health Status: Missing GROUP KIND NAMESPACE NAME STATUS HEALTH HOOK MESSAGE ConfigMap default helm-random-shapes-configmap OutOfSync Missing Service default helm-random-shapes-service OutOfSync Missing apps Deployment default helm-random-shapes-deploy OutOfSync Missing [I] [~/gitops] (22:32:20) ❯❯❯ argocd app get helm-random-shapes ⎈ (kind-kind/solar-system) Name: gitops/helm-random-shapes Project: default Server: https://kubernetes.default.svc Namespace: default URL: https://argocd.example.com/applications/helm-random-shapes Source: - Repo: https://github.com/sidd-harth/gitops-argocd Target: Path: helm-chart SyncWindow: Sync Allowed Sync Policy: Manual Sync Status: Synced to (05e2501) Health Status: Healthy GROUP KIND NAMESPACE NAME STATUS HEALTH HOOK MESSAGE ConfigMap default helm-random-shapes-configmap Synced configmap/helm-random-shapes-configmap created Service default helm-random-shapes-service Synced Healthy service/helm-random-shapes-service created apps Deployment default helm-random-shapes-deploy Synced Healthy deployment.apps/helm-random-shapes-deploy created [I] [~/gitops] (22:32:51) ❯❯❯ 
Enter fullscreen mode Exit fullscreen mode

2. Deploy Using Bitnami Helm Charts :
Add Bitnami Helm Charts Repo :

Create App :

You can also modify all the parameters : Change service.type from LoadBalancer to ClusterIP :


❯❯❯ k get all -n bitnami ⎈ (kind-kind/solar-system) NAME READY STATUS RESTARTS AGE pod/bitnami-helm-nginx-app-7bc9fc68c9-np9t7 0/1 Init:0/1 0 46s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/bitnami-helm-nginx-app ClusterIP 10.96.61.98 <none> 80/TCP,443/TCP 47s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/bitnami-helm-nginx-app 0/1 1 0 46s NAME DESIRED CURRENT READY AGE replicaset.apps/bitnami-helm-nginx-app-7bc9fc68c9 1 1 0 46s 
Enter fullscreen mode Exit fullscreen mode

4. ArgoCD Advanced

How ArgoCD manages role-based access control.

RBAC Architecture in Argo CD :

  • Users or Groups

    • Authenticated identities are then mapped to RBAC roles.
  • Roles

    • Logical sets of permissions.
    • Defined in the argocd-rbac-cm ConfigMap (in the argocd namespace).
    • Each role can contain multiple policy rules.
  • Policies (Rules)

    • Define what actions a role can perform on what resources.
    • Written in the format:
p, <role>, <resource>, <action>, <object>, <effect> 
Enter fullscreen mode Exit fullscreen mode

resource → what kind of object (applications, projects, repositories, clusters…)
action → get, create, update, delete, sync, override, etc.
object → * (all) or specific names

  • Role Bindings
    • Map users or groups to roles:
g, <username_or_group>, <role> 
Enter fullscreen mode Exit fullscreen mode

Give user jai permission to create clusters :

kubectl -n argocd patch configmap argocd-rbac-cm \ --patch='{"data":{"policy.csv":"p, role:create-cluster, clusters, create, *, allow\ng, jai, role:create-cluster"}}' # Test it: argocd account can-i create clusters '*' # should print: yes # (when logged in as jai) argocd account can-i delete clusters '*' # should print: no # (when logged in as jai) 
Enter fullscreen mode Exit fullscreen mode

Give user ali permission to manage applications in kia-project :

kubectl -n argocd patch configmap argocd-rbac-cm \ --patch='{"data":{"policy.csv":"p, role:kia-admins, applications, *, kia-project/*, allow\ng, ali, role:kia-admins"}}' # Test it: argocd account can-i sync applications kia-project/* # should print: yes # (when logged in as ali) 
Enter fullscreen mode Exit fullscreen mode

Notes

  • p = policy (what a role can do)
  • g = group binding (who gets the role)
  • argocd account can-i ... tests permissions for the currently logged in user
  • After patching, Argo CD automatically reloads the RBAC config (no restart needed)

User Management

  • The default ArgoCD installation has one built-in admin user that has full access to the system and is a super user.
  • It is advised to only utilize the admin user for initial settings and then disable it after adding all required users.
❯❯❯ argocd account list ⎈ (kind-kind/solar-system) NAME ENABLED CAPABILITIES admin true login 
Enter fullscreen mode Exit fullscreen mode
  • New users can be created and used for various roles.
  • New users can be defined using ArgoCD ConfigMap.
  • We edit the ArgoCD ConfigMap and add accounts.username record.
  • Each user can be associated with two capabilities, API key and login.
# Creates two local accounts: jai and ali kubectl -n argocd patch configmap argocd-cm \ --patch='{"data":{"accounts.jai": "apiKey,login"}}' kubectl -n argocd patch configmap argocd-cm \ --patch='{"data":{"accounts.ali": "apiKey,login"}}' 
Enter fullscreen mode Exit fullscreen mode

Grants them:

  • login → allows logging in via UI/CLI
  • apiKey → API key allows generating JSON Web Token authentication for APl access.
  • After this, you can bind them to RBAC roles using g, jai, role:... in the argocd-rbac-cm.
❯❯❯ kubectl -n gitops patch configmap argocd-cm \ --patch='{"data":{"accounts.jai": "apiKey,login"}}' configmap/argocd-cm patched ❯❯❯ kubectl -n gitops patch configmap argocd-cm \ --patch='{"data":{"accounts.ali": "apiKey,login"}}' configmap/argocd-cm patched ❯❯❯ argocd account list NAME ENABLED CAPABILITIES admin true login ali true apiKey, login jai true apiKey, login 
Enter fullscreen mode Exit fullscreen mode
❯❯❯ argocd account update-password --account jai *** Enter password of currently logged in user (admin): *** Enter new password for user jai: *** Confirm new password for user jai: Password updated 
Enter fullscreen mode Exit fullscreen mode
  • ArgoCD has two predefined default roles, read-only and admin.
    • The read-only role provides read-only access to all the resources, whereas the admin role provides unrestricted access to all resources.
    • And by default, the admin user is assigned to the admin role.
    • We can modify this and can also assign custom roles to users by editing the ArgoCD RBAC Config Map.
# set the default role in Argo CD RBAC to readonly kubectl -n argocd patch configmap argocd-rbac-cm \ --patch='{"data":{"policy.default": "role:readonly"}}' 
Enter fullscreen mode Exit fullscreen mode

In this example, we are patching the ArgoCD RBAC ConfigMap by assigning a default read-only role to any user who is not mapped to a specific role.

Bitnami Sealed Secrets

Summary of Flow :

  • Create normal Secret
  • Encrypt with kubeseal → SealedSecret
  • Commit to Git
  • Argo CD syncs it
  • sealed-secrets-controller decrypts to a real Secret

Step 1 — Create a normal Kubernetes Secret manifest

kubectl create secret generic mysql-password \ --from-literal=password='s1Ddh@rt#' \ --dry-run=client -o yaml > mysql-password_k8s-secret.yaml 
Enter fullscreen mode Exit fullscreen mode

This produces a file like:

apiVersion: v1 kind: Secret metadata: name: mysql-password data: password: czF1RGRoQHJ0Iw== 
Enter fullscreen mode Exit fullscreen mode

Step 2 — Install the Sealed Secrets controller (via Argo CD)

argocd app create sealed-secrets \ --repo https://bitnami-labs.github.io/sealed-secrets \ --helm-chart sealed-secrets \ --revision 2.2.0 \ --dest-server https://kubernetes.default.svc \ --dest-namespace kube-system # then sync  argocd app sync sealed # or  argocd app create sealed \ --repo https://bitnami-labs.github.io/sealed-secrets \ --helm-chart sealed-secrets \ --revision 2.2.0 \ --dest-server https://kubernetes.default.svc \ --dest-namespace sealed \ --sync-option CreateNamespace=true \ --sync-policy automated kubectl get all -n sealed kubectl -n sealed get deploy,sealedsecret,po,svc 
Enter fullscreen mode Exit fullscreen mode

Step 3 — Get the public certificate from the controller

# Export the cluster’s sealing public key: kubectl -n sealed get secret \ -l sealedsecrets.bitnami.com/sealed-secrets-key=active \ -o jsonpath='{.items[0].data.tls\.crt}' \ | base64 -d > sealedSecret.crt 
Enter fullscreen mode Exit fullscreen mode

Step 4 — Install the kubeseal CLI locally

wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.18.0/kubeseal-0.18.0-linux-amd64.tar.gz -O kubeseal.tar.gz tar -xzf kubeseal.tar.gz sudo install -m 755 kubeseal /usr/local/bin/kubeseal 
Enter fullscreen mode Exit fullscreen mode

Step 5 — Seal your secret

# Use kubeseal to convert your Secret into a SealedSecret: kubeseal -o yaml \ --scope cluster-wide \ --cert sealedSecret.crt \ < mysql-password_k8s-secret.yaml \ > mysql-password_sealed-secret.yaml 
Enter fullscreen mode Exit fullscreen mode

Now you have an encrypted SealedSecret manifest.

Step 6 — Commit the SealedSecret to Git (Argo CD watches it)

  • Push mysql-password_sealed-secret.yaml to your Git repo along with your deployment.yaml.
  • Argo CD will sync and apply it.
  • The sealed-secrets-controller pod will decrypt it and create the real Secret.

Step 7 — Verify the decrypted Secret

kubectl get secret mysql-password -o yaml 
Enter fullscreen mode Exit fullscreen mode

You should see the real base64-encoded password.

Hashicorp Vault

1. ArgoCD Vault Plugin CLI :
Step 1 -- Create Vault App in ArgoCD


❯❯❯ k get all -n vault-demo-gitops NAME READY STATUS RESTARTS AGE pod/vault-app-0 0/1 Running 0 2m2s pod/vault-app-agent-injector-67ff69f54-8dxqc 1/1 Running 0 2m3s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/vault-app ClusterIP 10.96.132.44 <none> 8200/TCP,8201/TCP 2m3s service/vault-app-agent-injector-svc ClusterIP 10.96.201.162 <none> 443/TCP 2m3s service/vault-app-internal ClusterIP None <none> 8200/TCP,8201/TCP 2m3s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/vault-app-agent-injector 1/1 1 1 2m3s NAME DESIRED CURRENT READY AGE replicaset.apps/vault-app-agent-injector-67ff69f54 1 1 1 2m3s NAME READY AGE statefulset.apps/vault-app 0/1 2m2s ❯❯❯ k get po -n vault-demo-gitops NAME READY STATUS RESTARTS AGE vault-app-0 0/1 Running 0 2m10s vault-app-agent-injector-67ff69f54-8dxqc 1/1 Running 0 2m11s 
Enter fullscreen mode Exit fullscreen mode

Step 2 --- Unsealed Process

# Access Vault UI kubectl -n vault-demo-gitops port-forward svc/vault-app 8200:8200 --address=0.0.0.0 # From UI:  # Key shares: 3 # Key threshold: 2 # Then Download Keys (root key and 3 sealed keys) , then UI will ask you about Unseal Keys and root key 
Enter fullscreen mode Exit fullscreen mode

Now: vault-app-0 will be 1/1 Ready state

❯❯❯ k get po -n vault-demo-gitops NAME READY STATUS RESTARTS AGE vault-app-0 1/1 Running 0 10m vault-app-agent-injector-67ff69f54-8dxqc 1/1 Running 0 10m 
Enter fullscreen mode Exit fullscreen mode
From UI: Enable new engine => KV => Next => Path: Credentials => Enable Engine => Create Secret => Path for this secret: app => Secret Data => Enter Random Vaules(username , password , apikey) => Save Now From Secrets, we can see credentials and from three dots => details , we can see path /credentials 
Enter fullscreen mode Exit fullscreen mode

Step 3 -- Create Secret with Template Syntax: vault.yaml

kind: Secret apiVersion: v1 metadata: name: app-crds annotations: avp.kubernetes.io/path: "credentials/data/app" type: Opaque stringData: apikey: <apikey> username: <username> password: <password> 
Enter fullscreen mode Exit fullscreen mode

Step 4 -- Install ArgoCD Vault Plugin

curl -L -o argocd-vault-plugin https://github.com/argoproj-labs/argocd-vault-plugin/releases/download/v1.17.0/argocd-vault-plugin_1.17.0_linux_amd64 chmod +x argocd-vault-plugin sudo mv argocd-vault-plugin /usr/local/bin/ # Verify installation argocd-vault-plugin version 
Enter fullscreen mode Exit fullscreen mode

Step 5 -- Create vault.env

VAULT_ADDR=http://localhost:8200 VAULT_TOKEN=<root token> AVP_TYPE=vault AVP_AUTH_TYPE=token 
Enter fullscreen mode Exit fullscreen mode
argocd-vault-plugin generate -c vault.env - < vault.yaml 
Enter fullscreen mode Exit fullscreen mode

will see as shown below :

apiVersion: v1 kind: Secret metadata: annotations: avp.kubernetes.io/path: credentials/data/app name: app-crds stringData: apikey: sdfshifsdifj596211af password: root9289 username: omar type: Opaque 
Enter fullscreen mode Exit fullscreen mode
❯❯❯ cat vault.yaml kind: Secret apiVersion: v1 metadata: name: app-crds annotations: avp.kubernetes.io/path: "credentials/data/app" type: Opaque stringData: apikey: <apikey> username: <username> password: <password> 
Enter fullscreen mode Exit fullscreen mode

2. ArgoCD with ArgoCD Vault Plugin :
we need to patch argocd-repo-server deployment and argocd-cm config map :

k edit -n gitops deployments.apps argocd-repo-server 
Enter fullscreen mode Exit fullscreen mode
apiVersion: apps/v1 kind: Deployment metadata: name: argocd-repo-server spec: template: spec: containers: - name: argocd-repo-server volumeMounts: - name: custom-tools mountPath: /usr/local/bin/argocd-vault-plugin subPath: argocd-vault-plugin # Note: AVP config (for the secret manager, etc) can be passed in several ways. This is just one example # https://argocd-vault-plugin.readthedocs.io/en/stable/config/ envFrom: - secretRef: name: argocd-vault-plugin-credentials volumes: - name: custom-tools emptyDir: {} initContainers: - name: download-tools image: alpine:3.8 command: [sh, -c] # Don't forget to update this to whatever the stable release version is # Note the lack of the `v` prefix unlike the git tag env: - name: AVP_VERSION value: "1.7.0" args: - >- wget -O argocd-vault-plugin https://github.com/argoproj-labs/argocd-vault-plugin/releases/download/v${AVP_VERSION}/argocd-vault-plugin_${AVP_VERSION}_linux_amd64 && chmod +x argocd-vault-plugin && mv argocd-vault-plugin /custom-tools/ volumeMounts: - mountPath: /custom-tools name: custom-tools # Not strictly necessary, but required for passing AVP configuration from a secret and for using Kubernetes auth to Hashicorp Vault automountServiceAccountToken: true 
Enter fullscreen mode Exit fullscreen mode
k edit -n gitops cm argocd-cm 
Enter fullscreen mode Exit fullscreen mode
data: configManagementPlugins: |- - name: argocd-vault-plugin generate: command: ["argocd-vault-plugin"] args: ["generate", "./"] 
Enter fullscreen mode Exit fullscreen mode
k rollout restart deploy argocd-repo-server 
Enter fullscreen mode Exit fullscreen mode
From UI: Create App => URL: https://github.com/sidd-harth/gitops-argocd path: ./vault-secrets under the namespace option , we will find Directory Icon, switch from Directory to Plugin => Name: argocd-vault-plugin => Env: VAULT_ADDR=http://vault-app.vault-demo.svc.cluster.local:8200 VAULT_TOKEN=<root token> AVP_TYPE=vault AVP_AUTH_TYPE=token => Create 
Enter fullscreen mode Exit fullscreen mode
# to test k -n vault-secret get secrets -o yaml 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)