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)