ArgoCD Setup on Kubernetes — Complete Installation and App Deployment Guide

ArgoCD syncing a Git repository to a Kubernetes cluster in a GitOps workflow with automated deployment pipeline

You’ve written your Kubernetes manifests, pushed them to Git, and now you need a reliable way to keep your cluster in sync with what’s in the repo. Manually running kubectl apply works until it doesn’t — a missed apply, a direct cluster edit, or a hotfix that never made it back to Git, and your cluster is in an unknown state.

ArgoCD solves this with a simple guarantee: your cluster should look exactly like what’s in Git. If it doesn’t, ArgoCD tells you — and can automatically fix it. This is the core of GitOps, and ArgoCD is the most widely adopted tool for implementing it on Kubernetes.

This guide walks you through a complete ArgoCD installation on Kubernetes, deploying your first application, and the configuration that actually matters in production.


What Is ArgoCD?

ArgoCD is a declarative GitOps continuous delivery tool for Kubernetes. It runs inside your cluster, watches one or more Git repositories, and ensures the live cluster state matches what’s defined in those repos.

If you want to understand the GitOps model and how ArgoCD compares to Flux before going hands-on, start with our ArgoCD overview and the Flux vs ArgoCD comparison. This guide assumes you’ve made the call to go with ArgoCD and want to get it running.

How it works in one sentence: ArgoCD polls your Git repo (or receives a webhook), compares the desired state in Git against the live state in the cluster, and either alerts you to drift or automatically syncs the difference.


Prerequisites

Before you start:

  • A running Kubernetes cluster (local with kind/minikube, or managed — EKS, AKS, GKE, DigitalOcean Kubernetes all work)
  • kubectl configured and pointing at your cluster
  • git installed locally
  • A Git repository to use as your GitOps source (GitHub, GitLab, Bitbucket all work)

If you’re new to the core Kubernetes tooling, our Kubernetes commands reference covers the kubectl basics you’ll need throughout this guide.


Step 1 — Install ArgoCD

ArgoCD runs in its own namespace. Create it and apply the install manifest:

# Create the argocd namespace
kubectl create namespace argocd

# Install ArgoCD
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

Wait for all pods to be running:

kubectl wait --for=condition=available --timeout=300s deployment/argocd-server -n argocd

Verify everything is up:

kubectl get pods -n argocd

You should see these pods in Running state:

argocd-application-controller-0
argocd-applicationset-controller-xxx
argocd-dex-server-xxx
argocd-notifications-controller-xxx
argocd-redis-xxx
argocd-repo-server-xxx
argocd-server-xxx


Step 2 — Access the ArgoCD UI

By default, the ArgoCD server service is of type ClusterIP — not externally accessible. For initial setup, use port-forwarding:

kubectl port-forward svc/argocd-server -n argocd 8080:443

Open https://localhost:8080 in your browser. You’ll get a certificate warning — this is expected with the default self-signed cert. Proceed anyway for now.

Get the initial admin password:

kubectl get secret argocd-initial-admin-secret \
  -n argocd \
  -o jsonpath="{.data.password}" | base64 -d && echo

Log in with username admin and the password from the command above.

Change the admin password immediately after first login — go to User Info → Update Password in the UI, or via CLI:

argocd account update-password \
  --current-password <initial-password> \
  --new-password <your-new-password>


Step 3 — Install the ArgoCD CLI

The CLI isn’t strictly required, but it’s far faster for most operations than the UI:

# macOS
brew install argocd

# Linux
curl -sSL -o argocd-linux-amd64 \
  https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
rm argocd-linux-amd64

# Verify
argocd version

Log in via CLI (while port-forward is running):

argocd login localhost:8080 \
  --username admin \
  --password <your-password> \
  --insecure


Step 4 — Deploy Your First Application

ArgoCD uses an Application custom resource to define what to deploy and where to get it from. You can create this via UI, CLI, or YAML — YAML is the GitOps-native way.

Option A — Via YAML (recommended)

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: argocd           # Application resource lives in argocd namespace
spec:
  project: default
  source:
    repoURL: https://github.com/your-org/your-k8s-manifests.git
    targetRevision: HEAD       # Track the default branch
    path: apps/my-app          # Path inside the repo where manifests live
  destination:
    server: https://kubernetes.default.svc   # Deploy to the same cluster ArgoCD runs in
    namespace: production
  syncPolicy:
    automated:
      prune: true              # Delete resources removed from Git
      selfHeal: true           # Auto-fix drift if someone edits the cluster directly
    syncOptions:
      - CreateNamespace=true   # Create the destination namespace if it doesn't exist

Apply it:

kubectl apply -f my-app.yaml

Option B — Via CLI

argocd app create my-app \
  --repo https://github.com/your-org/your-k8s-manifests.git \
  --path apps/my-app \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace production \
  --sync-policy automated \
  --auto-prune \
  --self-heal

Check the application status

# Via CLI
argocd app get my-app

# Via kubectl
kubectl get application my-app -n argocd

The STATUS field will show one of:

  • Synced — cluster matches Git
  • OutOfSync — cluster has drifted from Git
  • Unknown — ArgoCD can’t determine the state

Step 5 — Connect a Private Repository

If your manifests repo is private, ArgoCD needs credentials. For GitHub, the recommended approach is an SSH key or a GitHub App — avoid using personal access tokens in production.

SSH key method:

# Generate a deploy key (no passphrase)
ssh-keygen -t ed25519 -C "argocd@yourcluster" -f ~/.ssh/argocd_deploy_key -N ""

# Add the public key to your GitHub repo
# GitHub repo → Settings → Deploy keys → Add deploy key
cat ~/.ssh/argocd_deploy_key.pub

# Register the private key with ArgoCD
argocd repo add git@github.com:your-org/your-k8s-manifests.git \
  --ssh-private-key-path ~/.ssh/argocd_deploy_key

Verify the repo is connected:

argocd repo list


Step 6 — Sync Policies Explained

This is where most teams get configuration wrong. Here’s what each sync policy option actually does:

syncPolicy:
  automated:
    prune: true       # IMPORTANT: without this, deleted resources in Git stay in the cluster
    selfHeal: true    # Without this, manual kubectl edits won't be reverted
  syncOptions:
    - CreateNamespace=true      # Auto-create target namespace
    - PrunePropagationPolicy=foreground   # Wait for dependent resources before pruning
    - ApplyOutOfSyncOnly=true   # Only apply resources that have actually changed (faster)
    - ServerSideApply=true      # Use server-side apply (better for large resources)
  retry:
    limit: 5                    # Retry failed syncs up to 5 times
    backoff:
      duration: 5s
      factor: 2
      maxDuration: 3m

The prune flag is the one that surprises people most. Without it, if you delete a Deployment from Git, it keeps running in the cluster. ArgoCD considers this “OutOfSync” but won’t remove it. In most setups you want prune: true — just make sure your team understands that removing something from Git means removing it from the cluster.

selfHeal: true is critical for a true GitOps workflow. Without it, someone can kubectl edit a resource directly and the change sticks. With it, ArgoCD reverts any out-of-band changes within the sync interval (default 3 minutes).


Step 7 — RBAC for ArgoCD

ArgoCD has its own RBAC system on top of Kubernetes RBAC. By default, all authenticated users get read-only access. You configure project-level and cluster-level permissions via the argocd-rbac-cm ConfigMap.

Before configuring ArgoCD RBAC, make sure your underlying Kubernetes RBAC is properly scoped — ArgoCD’s service account needs specific cluster permissions and those should follow least-privilege principles.

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
data:
  policy.default: role:readonly     # Default role for all authenticated users
  policy.csv: |
    # Admins — full access
    p, role:admin, applications, *, */*, allow
    p, role:admin, clusters, get, *, allow
    p, role:admin, repositories, *, *, allow

    # Developers — deploy only to staging, read everywhere
    p, role:developer, applications, get, */*, allow
    p, role:developer, applications, sync, */staging-*, allow
    p, role:developer, applications, action/*, */staging-*, allow

    # Bind SSO groups to roles
    g, your-org:platform-team, role:admin
    g, your-org:dev-team, role:developer


Step 8 — Expose ArgoCD Properly (Production)

Port-forwarding is fine for local access. In production you need a proper ingress. Here’s an example using the NGINX ingress controller:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: argocd-server-ingress
  namespace: argocd
  annotations:
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
  ingressClassName: nginx
  rules:
    - host: argocd.yourdomain.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: argocd-server
                port:
                  number: 443
  tls:
    - hosts:
        - argocd.yourdomain.com
      secretName: argocd-tls

Note ssl-passthrough: "true" — ArgoCD server terminates TLS itself, so you pass the TLS connection through the ingress rather than terminating at the ingress controller.

To lock down who can reach the ArgoCD UI at the network level, pair this with Kubernetes Network Policies restricting ingress to the argocd namespace from your ingress controller only.


Step 9 — ApplicationSets for Multi-Environment Deployments

Manually creating one Application per environment gets tedious fast. ApplicationSet generates multiple Application resources from a template — one definition to manage dev, staging, and production.

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: my-app-environments
  namespace: argocd
spec:
  generators:
    - list:
        elements:
          - env: dev
            namespace: development
            revision: develop
          - env: staging
            namespace: staging
            revision: main
          - env: production
            namespace: production
            revision: main
  template:
    metadata:
      name: "my-app-{{env}}"
    spec:
      project: default
      source:
        repoURL: https://github.com/your-org/your-k8s-manifests.git
        targetRevision: "{{revision}}"
        path: "apps/my-app/{{env}}"
      destination:
        server: https://kubernetes.default.svc
        namespace: "{{namespace}}"
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true

This creates three ArgoCD Applications — my-app-dev, my-app-staging, my-app-production — all managed from a single ApplicationSet definition.


Common Mistakes and How to Avoid Them

1. Not enabling prune: true Resources deleted from Git keep running in the cluster. Your Git repo and cluster drift silently over time. Always set prune: true unless you have a specific reason not to.

2. Storing secrets directly in Git ArgoCD syncs everything in your repo to the cluster — including Kubernetes Secret manifests if you commit them. Never commit raw secrets to Git. Use Sealed Secrets, External Secrets Operator, or HashiCorp Vault. This is one of the most critical security mistakes in GitOps setups.

3. Using HEAD tracking on production Tracking HEAD means every merge to your main branch immediately syncs to production. For production, pin to a specific tag or use a separate branch with a manual promotion step:

targetRevision: v1.4.2    # Pin to a release tag for production

4. One repo for everything Putting application code and Kubernetes manifests in the same repo creates noise — every code commit triggers an ArgoCD sync check. Separate your app code repo from your GitOps manifests repo.

5. Skipping health checks ArgoCD has built-in health checks for standard resources (Deployment, StatefulSet, etc.). For custom resources, you need to define custom health check scripts in argocd-cm. Without them, ArgoCD reports Healthy even when your application is broken.


Best Practices

  • Use Projects to group related Applications and set RBAC boundaries between teams
  • Enable notifications — ArgoCD Notifications can post to Slack, Teams, or PagerDuty on sync failures, health degradation, and successful deployments
  • Use argocd app diff before manual syncs to see exactly what will change
  • Set resource exclusions for resources you don’t want ArgoCD to manage (e.g., certain ConfigMaps managed by operators)
  • Monitor ArgoCD itself — scrape argocd-metrics and argocd-server-metrics with Prometheus. Key metrics: argocd_app_info, argocd_app_sync_total, argocd_app_health_status
  • Back up ArgoCD state — all Application definitions should live in Git themselves (App of Apps pattern), so you can recover a wiped ArgoCD installation by re-applying your app definitions

What’s Next

With ArgoCD running and your first application deployed, the natural next steps in the GitOps cluster are:

  • GitHub Actions + Kubernetes — wire your CI pipeline to update image tags in the manifests repo, triggering ArgoCD to sync automatically. Check out our introduction to GitHub Actions if you need the CI fundamentals first.
  • Helm with ArgoCD — deploying Helm charts via ArgoCD, overriding values per environment
  • ArgoCD Image Updater — automatically update image tags in Git when a new image is pushed to your registry
  • Notifications — configuring Slack/Teams alerts for sync failures and deployment events

If you’re evaluating whether to use ArgoCD or Flux for your GitOps setup, our Flux vs ArgoCD comparison breaks down the trade-offs in detail.

GitOps fundamentally changes how your team interacts with the cluster — instead of running kubectl apply, you make a PR. That shift in workflow takes a week to get used to and pays back for years.