Configuring ArgoCD to Access Private GitHub Repositories — hero banner

December 16, 2025·4 min read

This beginner's guide covers how to configure ArgoCD to sync applications from private GitHub repositories and pull private container images from GitHub Container Registry (GHCR). For production environments, consider using External Secrets Operator or Sealed Secrets to manage credentials declaratively rather than imperatively


Prerequisites

  • ArgoCD installed on your Kubernetes cluster
  • A GitHub Personal Access Token (PAT) with the following scopes:
    • repo - Full control of private repositories
    • read:packages - Read packages from GHCR
    • write:packages - Push packages to GHCR (for CI/CD)

Part 1: ArgoCD Git Repository Access

ArgoCD needs credentials to clone private repositories. Create a Kubernetes secret with your GitHub username and PAT.

Create the Secret

apiVersion: v1
kind: Secret
metadata:
  name: github-repo-creds
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: repo-creds
type: Opaque
stringData:
  url: https://github.com/YOUR_USERNAME/
  type: git
  username: YOUR_USERNAME
  password: YOUR_GITHUB_PAT

Key points:

  • Use repo-creds as the secret type for prefix matching (applies to all repos under that URL)
  • The username field should be your actual GitHub username
  • The password field is your PAT
  • Include the trailing slash on the URL

Apply and Restart

kubectl apply -f github-repo-creds.yaml
kubectl rollout restart deployment argocd-repo-server -n argocd

Verify

Check that your application syncs:

kubectl get application -n argocd

You should see Synced status instead of Unknown.


Part 2: Private Container Image Pulls

If your container images are also private in GHCR, Kubernetes needs an imagePullSecret to authenticate when pulling images.

Create the Docker Registry Secret

Run this for each namespace where you deploy applications:

kubectl create secret docker-registry ghcr-pull-secret \
  --namespace YOUR_NAMESPACE \
  --docker-server=ghcr.io \
  --docker-username=YOUR_USERNAME \
  --docker-password=YOUR_GITHUB_PAT \
  --docker-email=noreply@github.com

Reference in Your Helm Chart

Add to your values.yaml:

imagePullSecrets:
  - name: ghcr-pull-secret

Update your deployment template to use it:

spec:
  template:
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"

Storing the PAT Securely

Instead of hardcoding the PAT, store it in a secret manager like Azure Key Vault and retrieve it when creating secrets:

PAT=$(az keyvault secret show --vault-name YOUR_VAULT --name github-pat --query value -o tsv)

kubectl create secret docker-registry ghcr-pull-secret \
  --namespace dev \
  --docker-server=ghcr.io \
  --docker-username=YOUR_USERNAME \
  --docker-password="$PAT" \
  --docker-email=noreply@github.com

Summary

Secret Purpose Namespace
github-repo-creds ArgoCD clones private repos argocd
ghcr-pull-secret Kubernetes pulls private images Each deployment namespace

Both secrets use the same PAT with your GitHub username. The repo-creds type enables prefix matching, so one secret covers all repositories under your GitHub account.


Production Approach: External Secrets Operator

The imperative approach above works, but for production you want secrets managed declaratively through GitOps. External Secrets Operator automatically syncs secrets from Azure Key Vault (or AWS Secrets Manager, HashiCorp Vault, etc.) into Kubernetes secrets.

Install External Secrets Operator

helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets \
  --namespace external-secrets \
  --create-namespace

Configure Azure Key Vault Access

First, create a SecretStore that connects to your Key Vault. This example uses Azure Workload Identity:

apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: azure-keyvault
spec:
  provider:
    azurekv:
      authType: WorkloadIdentity
      vaultUrl: "https://YOUR_VAULT.vault.azure.net"
      serviceAccountRef:
        name: external-secrets-sa
        namespace: external-secrets

Declarative GitHub Credentials for ArgoCD

Now define an ExternalSecret that pulls the PAT from Key Vault and creates the ArgoCD secret:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: github-repo-creds
  namespace: argocd
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: azure-keyvault
    kind: ClusterSecretStore
  target:
    name: github-repo-creds
    template:
      metadata:
        labels:
          argocd.argoproj.io/secret-type: repo-creds
      data:
        type: git
        url: https://github.com/YOUR_USERNAME/
        username: YOUR_USERNAME
        password: "{{ .github_pat }}"
  data:
    - secretKey: github_pat
      remoteRef:
        key: github-pat

Declarative GHCR Pull Secret

Similarly for the image pull secret:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: ghcr-pull-secret
  namespace: dev
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: azure-keyvault
    kind: ClusterSecretStore
  target:
    name: ghcr-pull-secret
    template:
      type: kubernetes.io/dockerconfigjson
      data:
        .dockerconfigjson: |
          {
            "auths": {
              "ghcr.io": {
                "username": "YOUR_USERNAME",
                "password": "{{ .github_pat }}",
                "auth": "{{ printf "%s:%s" "YOUR_USERNAME" .github_pat | b64enc }}"
              }
            }
          }
  data:
    - secretKey: github_pat
      remoteRef:
        key: github-pat

Benefits of This Approach

  1. GitOps-friendly - Secret definitions live in git, actual values stay in Key Vault
  2. Automatic rotation - Change the PAT in Key Vault, secrets update automatically
  3. Audit trail - Key Vault logs all secret access
  4. No imperative commands - Everything is declarative YAML
  5. Single source of truth - One PAT in Key Vault, referenced everywhere

Alternative: Sealed Secrets

If you don't have a secret manager, Sealed Secrets lets you encrypt secrets so they can be committed to git:

# Install kubeseal CLI and controller
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets -n kube-system

# Encrypt a secret
kubectl create secret generic github-repo-creds \
  --namespace argocd \
  --from-literal=password=YOUR_PAT \
  --dry-run=client -o yaml | kubeseal -o yaml > sealed-secret.yaml

The sealed secret can be safely committed to git - only your cluster can decrypt it.


Which Approach Should You Use?

Approach Best For
Manual kubectl create Learning, development, quick setup
External Secrets Operator Production with existing secret manager
Sealed Secrets Production without external secret manager

For most production Kubernetes environments, External Secrets Operator with your cloud provider's secret manager is the recommended approach.

Enjoyed this post? Give it a clap!

Comments