Why Argo CD + GitOps?
If you only have one K8s cluster with low update frequency, manual deployment with kubectl apply is fine. But once the architecture grows and you hit the following scenarios, manual operations start to fall short:
- Need to manage 10–20 K8s clusters (Dev/Staging/Prod and other environments)
- Frequently switching
kubectl contextis error-prone - After manual Apply, hard to confirm whether the cluster's current state matches version control
This is where introducing GitOps + Argo CD effectively addresses these pain points.
The core idea is intuitive: "automate the deployment operation, reduce human intervention."
- Git as the single source of truth: All YAML and Helm values must go into Git version control.
- Argo CD handles auto-sync: Continuously monitors the Git repo; when code changes, it automatically syncs cluster state to the desired state.
- Fast rollback: If a deploy goes wrong, just Revert in Git and Argo CD will help switch back to the previous version, ensuring stability.
These notes record the key steps for setting up this flow: installing Argo CD via Helm, configuring GitLab SSO, and how to plan permission management properly.
Core GitOps concept
I won't go too deep into theory, but one concept is essential.
Traditional deployment often lacks standardization — what each person manually applies may differ, and over time the environment "drifts" and becomes hard to trace. GitOps treats Git as the "standard SOP," and Argo CD is the executor. It ensures the cluster's actual state (Live State) always matches the definition in the Git repo (Desired State). If someone manually modifies cluster resources, Argo CD detects the difference and marks it as OutOfSync — you can even configure self-healing to revert it.
Once you understand this, the configuration steps below have more context.
(Prerequisites like adding the Helm Repo are skipped here — let's jump to the main course of SSO and permissions.)
Implementation: settle authentication and installation
Step 1: Prepare the Helm environment
Easy step — add the Helm repo and export the Argo CD default values.yaml for editing.
# Add Helm repository
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
# Export default values for editing
helm show values argo/argo-cd --version 8.3.2 > values.yaml
Step 2: Create a GitLab OAuth Application
This step establishes trust between Argo CD and GitLab. By configuring an Application in GitLab, users can log in to Argo CD with their GitLab account — for unified account management.
- Log in to GitLab → top-right avatar →
Settings → Applications - Add an Application:
- Name:
ArgoCD SSO - Redirect URI:
http://<your-node-ip>:32009/api/dex/callback - Scopes: check
openid,read_user,email
- After saving, get the Application ID and Secret
⚠️ Don't commit the Client ID and Secret to a Git repository.

Two configuration methods:
A. Write directly into values.yaml (recommended — easy to automate) B. Configure inside Argo CD UI after install (good for ad-hoc adjustments)

Step 3: Create a GitLab Group Access Token
Argo CD needs permission to access the Git repo to pull code. Strongly recommend not using a personal account's Token — staff changes or password updates would interrupt production deployments. Best practice: get a dedicated Group Access Token.
- Go to your GitLab Group page →
Settings→Access Tokens. - Create a Token:
- Name: something recognizable, e.g.,
argocd-group-readonly - Scopes: just check
read_repositoryandread_api— least privilege is safest. - Role: pick
Reporter(read-only suffices).
- Critical: copy the Token immediately after creation — GitLab only displays it once.
Step 4: Write values.yaml
This is the key step. We'll create a custom values.yaml filling in the SSO and Repo info gathered above, overriding Helm's default settings.
configs:
cm:
# Argo CD's entrypoint URL — used by SSO callback
url: http://<your-node-ip>:32009
# The most likely place to get stuck: Dex (SSO) configuration
dex.config: |
connectors:
- type: gitlab
id: gitlab
name: GitLab
config:
baseURL: https://gitlab.example.com
clientID: <your-gitlab-oauth-client-id>
clientSecret: <your-gitlab-oauth-client-secret>
# Make sure the Callback URL in GitLab Application settings matches this
redirectURI: http://<your-node-ip>:32009/api/dex/callback
# Plug in the Token you just created so Argo CD recognizes your GitLab
credentialTemplates:
gitlab-group-token:
url: https://gitlab.example.com
username: oauth2 # always 'oauth2'
password: <your-gitlab-group-access-token>
# Pre-register repos to save adding them one-by-one in the UI later
repositories:
kong-api-gateway:
url: https://gitlab.example.com/your-group/kong-api-gateway.git
type: git
argocd-deployment:
url: https://gitlab.example.com/your-group/argocd-deployment.git
type: git
# Server network setup (Lab uses NodePort for laziness; production should use Ingress)
server:
service:
type: NodePort
nodePortHttp: 32009
nodePortHttps: 32010
# Allow HTTP in dev — otherwise certificate errors will keep popping up
extraArgs:
- --insecure
💡 Production recommendation: Ingress + TLS to replace NodePort, and remove
--insecure.
Step 5: Install Argo CD
Once configs are ready, fire away:
# Install Argo CD
helm upgrade --install homelab-argo argo/argo-cd \
--version 8.3.2 \
-f values.yaml \
-n argocd --create-namespace
# Wait for Pods to come up
kubectl wait --for=condition=ready pod \
-l app.kubernetes.io/name=argocd-server \
-n argocd --timeout=300s
# Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d
Change the admin password right after first login.
Permission management
Step 6: Configure RBAC and AppProject
After installing Argo CD, you still need to define "who can manage what resources" and "which clusters can be deployed to." The AppProject mechanism is essentially a permission whitelist — without proper config, Argo CD won't be able to deploy anything.
Create argocd-projects.yaml:
apiVersion: v1
kind: List
items:
# Default Project — basic permissions
- apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: default
namespace: argocd
spec:
# Allow all source repos
sourceRepos:
- '*'
# Allow deploy to all clusters and namespaces
destinations:
- namespace: '*'
server: '*'
# Allow operating cluster-level resources
clusterResourceWhitelist:
- group: '*'
kind: '*'
# Allow operating namespace-level resources
namespaceResourceWhitelist:
- group: '*'
kind: '*'
# Custom Project — tune to your needs
- apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: your-project
namespace: argocd
spec:
description: Your project description
sourceRepos:
- 'https://gitlab.example.com/your-group/*'
destinations:
- namespace: '*'
server: https://kubernetes.default.svc
# Cluster-level resource whitelist
clusterResourceWhitelist:
- group: ''
kind: Namespace
- group: 'rbac.authorization.k8s.io'
kind: ClusterRole
- group: 'rbac.authorization.k8s.io'
kind: ClusterRoleBinding
- group: 'apiextensions.k8s.io'
kind: CustomResourceDefinition
# Namespace-level resource whitelist
namespaceResourceWhitelist:
- group: ''
kind: ConfigMap
- group: ''
kind: Secret
- group: ''
kind: Service
- group: ''
kind: ServiceAccount
- group: 'apps'
kind: Deployment
- group: 'apps'
kind: StatefulSet
- group: 'batch'
kind: Job
- group: 'batch'
kind: CronJob
- group: 'networking.k8s.io'
kind: Ingress
# Extra RBAC permissions for Argo CD Controller (supports metrics-server, etc.)
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: argocd-application-controller-auth-delegator
labels:
app.kubernetes.io/component: application-controller
app.kubernetes.io/name: argocd-application-controller
app.kubernetes.io/part-of: argocd
rules:
# Auth delegation for metrics-server
- apiGroups: ["authentication.k8s.io"]
resources: ["tokenreviews"]
verbs: ["create"]
- apiGroups: ["authorization.k8s.io"]
resources: ["subjectaccessreviews"]
verbs: ["create"]
# API Services management
- apiGroups: ["apiregistration.k8s.io"]
resources: ["apiservices"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: argocd-application-controller-auth-delegator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: argocd-application-controller-auth-delegator
subjects:
- kind: ServiceAccount
name: argocd-application-controller
namespace: argocd
Apply the config
# Apply AppProject and RBAC config
kubectl apply -f argocd-projects.yaml -n argocd
# Verify Projects are created
kubectl get appprojects -n argocd
# Check RBAC permissions too
kubectl get clusterrole | grep argocd
kubectl get clusterrolebinding | grep argocd
Permission structure cheat sheet
When integrating Argo CD with GitLab, permissions split into three layers:
- SSO login (GitLab OAuth): manages user identity — decides who can log in to the Argo CD UI.
- Repo access (Group Access Token): manages code access — lets Argo CD read GitLab repos.
- Registry (optional): manages image-pull permissions — pulling private images requires an additional Deploy Token.
💡 SSO manages "people," Group Token manages "code," Registry Token manages "images."

Worked example
Application status overview

This is the Argo CD Application status page. Key indicators to watch:
- Application name:
testapp - APP HEALTH: ✅ Healthy — all resources running normally.
- SYNC STATUS: ✅ Synced to HEAD — Git config is fully synced to the cluster.
- LAST SYNC: ✅ Sync OK — most recent sync succeeded.
- Auto sync: ❌ Auto-sync not enabled.
🌳 Application resource tree
A K8s resource tree deployed via GitOps roughly looks like this:
- testapp (Application)
- nginx-content (ConfigMap)
- test-app (Namespace)
- nginx-test-service (Service)
- nginx-test (Deployment)
- nginx-test-554867cd4b (ReplicaSet)
- nginx-test-554867cd4b-65gtp (Pod)
- nginx-test-554867cd4b-xgs9x (Pod)
- nginx-test-554867cd4b (ReplicaSet)
📌 As long as the top-right shows ✅ green status, the deployment succeeded.
Quick command reference
A quick reference for common ops commands.
Helm
# Install Helm (Windows)
choco install kubernetes-helm
# Install/upgrade Argo CD
helm upgrade --install argocd argo/argo-cd \
--namespace argocd --create-namespace \
-f values.yaml
# Export current settings
helm get values argocd -n argocd -o yaml > current-values.yaml
K8s debug and config commands
# Inspect Argo CD Service
kubectl get svc argocd-server -n argocd
# Patch Service to NodePort
kubectl patch svc argocd-server -n argocd -p '{
"spec": {
"type": "NodePort",
"ports": [
{
"port": 80,
"targetPort": 8080,
"nodePort": 32009
}
]
}
}'
# Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d
# Export AppProject config
kubectl get appprojects -n argocd -o yaml > argocd-projects.yaml
⚠️ Once you have the admin password, change it immediately.




























Comments