Skip to main content
  1. Posts/
  2. Learning ArgoCD/
  3. Getting Started/

Deploying a Sample App via ArgoCD

·736 words·4 mins
Ravi Singh
Author
Ravi Singh
Software engineer with 15+ years building backend systems and cloud platforms across fintech, automotive, and academia. I write about the things I build, debug, and learn — so I don’t forget them.
Learning ArgoCD - This article is part of a series.
Part 2: This Article

Deploying a Sample App via ArgoCD
#

What We Built
#

A minimal nginx app managed entirely by ArgoCD using Kustomize overlays.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
config/sample-app/
  base/
    deployment.yaml          # nginx:1.27, replicas: 2
    service.yaml             # ClusterIP on port 80
    kustomization.yaml
  overlays/
    local/
      kustomization.yaml     # references ../../base

apps/sample-app/
  application.yaml           # ArgoCD Application CRD

bootstrap/
  repo-secret.yaml           # git-ignored - applied manually once

Key Concepts
#

The GitOps Loop
#

1
Push to Git → ArgoCD detects change → Syncs to cluster → Cluster matches Git

ArgoCD continuously compares desired state (Git) against actual state (cluster). Any drift is automatically corrected because selfHeal: true is set in the sync policy.

We tested this by bumping replicas: 1 → 2, pushing to GitHub, and watching ArgoCD reconcile without any manual intervention.

The Bootstrap Problem
#

To sync a private repo, ArgoCD needs credentials - but you can’t store credentials in the repo you’re trying to protect. This is a chicken-and-egg problem every GitOps setup must solve.

Options:

ApproachFully GitOps?Notes
kubectl apply a Secret once, never commit~90%Simplest for learning
Sealed Secrets100%Encrypt secret, commit ciphertext
SOPS + age100%File-level encryption

We chose: apply a Kubernetes Secret manually once (bootstrap step). ArgoCD auto-detects any Secret in the argocd namespace labelled argocd.argoproj.io/secret-type: repository.

The secret lives in bootstrap/ which is git-ignored:

1
2
3
4
5
6
7
8
metadata:
  labels:
    argocd.argoproj.io/secret-type: repository
stringData:
  type: git
  url: https://github.com/ravikrs/learning-argocd
  username: ravikrs
  password: <PAT>

Apply it once after a fresh cluster install:

1
kubectl apply -f bootstrap/repo-secret.yaml

Reconciliation on Non-Manifest Pushes
#

ArgoCD watches the entire repo for HEAD changes but only renders and diffs the path specified in the Application (path: config/sample-app/overlays/local). This means:

  • Push to docs/ or backlog.md → ArgoCD detects HEAD changed → re-renders manifests at the path → sees no diff → does nothing to the cluster
  • No k8s operations happen, but there is a lightweight reconciliation cycle

There is no native ArgoCD feature to skip reconciliation based on which files changed. The correct solution used in real GitOps setups is repo separation:

RepoContainsWho pushes
app-backendSource code, docs, testsDevelopers
app-backend-configKubernetes manifests onlyCI/CD pipeline or humans deploying

ArgoCD only watches the config repo. A push to the app repo has zero effect on ArgoCD until CI/CD explicitly updates the config repo (e.g. bumps the image tag). This is the standard pattern for production GitOps pipelines.

In this learning repo: the repo is the config repo, so the separation doesn’t apply. Harmless reconciliation cycles on doc/backlog pushes are an acceptable trade-off for simplicity.

Polling vs Webhooks
#

ArgoCD polls Git every 3 minutes by default. This is fine for learning but too slow for production.

Faster option - GitHub webhook:

  • GitHub pushes a notification to ArgoCD on every commit
  • ArgoCD syncs in seconds, not minutes
  • Requires ArgoCD to be reachable from the internet (use ngrok for local setups)

See backlog: GitHub Webhook for Instant Sync.


ArgoCD Application Manifest
#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
spec:
  source:
    path: config/sample-app/overlays/local   # Kustomize overlay ArgoCD renders
  destination:
    namespace: sample-app
  syncPolicy:
    automated:
      prune: true      # delete resources removed from Git
      selfHeal: true   # revert any manual changes to the cluster
    syncOptions:
      - CreateNamespace=true   # create namespace if it doesn't exist

prune: true - if you delete a file from Git, ArgoCD removes the resource from the cluster. selfHeal: true - if someone runs kubectl edit directly on the cluster, ArgoCD reverts it to match Git.


Commands Used
#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Validate Kustomize renders before pushing
kubectl kustomize config/sample-app/overlays/local

# Register the repo (after applying bootstrap/repo-secret.yaml)
argocd repo list

# Apply the ArgoCD Application
kubectl apply -f apps/sample-app/application.yaml

# Check sync status and health
argocd app get sample-app

# Force an immediate sync (skip the 3-min poll)
argocd app sync sample-app

# Verify pods
kubectl get pods -n sample-app

Gotchas
#

  • .idea/ committed in first commit - IDE folder slipped in before .gitignore was created. Added .idea/ to .gitignore and committed a fix.
  • PAT scope - the GitHub fine-grained PAT only needs Contents: Read-only for the target repo. No broader permissions required.
  • HTTPS vs SSH - ArgoCD Application manifests use the HTTPS repo URL. The local git remote can use SSH - they’re independent.
Learning ArgoCD - This article is part of a series.
Part 2: This Article