This is part of the Vault + Kubernetes Integration Guide. Return to the main guide for the full architecture overview.


The ArgoCD Vault Plugin (AVP) bridges the gap between GitOps and secrets management. It processes your Kubernetes manifests at sync time, replacing placeholder tokens with real values from Vault — so you never commit actual secrets to Git.

The Problem AVP Solves

WITHOUT AVP:
┌──────────┐                    ┌─────────────┐
│   Git    │  ❌ Real secrets   │  Kubernetes  │
│          │──────────────────►│             │
│          │  committed to Git  │             │
└──────────┘                    └─────────────┘

WITH AVP:
┌──────────┐  Placeholders  ┌──────────┐  Resolved  ┌─────────────┐
│   Git    │──────────────►│  ArgoCD  │──────────►│  Kubernetes  │
│ <secret> │               │  + AVP   │           │  (real vals) │
└──────────┘               └────┬─────┘           └─────────────┘
                                │
                           ┌────▼─────┐
                           │  Vault   │
                           └──────────┘

Installation

Patch the argocd-repo-server to download AVP at startup:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: argocd-repo-server
  namespace: argocd
spec:
  template:
    spec:
      serviceAccountName: argocd-repo-server
      initContainers:
        - name: download-avp
          image: alpine:3.19
          command: [sh, -c]
          args:
            - |
              wget -q -O /custom-tools/argocd-vault-plugin \
                https://github.com/argoproj-labs/argocd-vault-plugin/releases/download/v1.18.1/argocd-vault-plugin_1.18.1_linux_amd64
              chmod +x /custom-tools/argocd-vault-plugin
          volumeMounts:
            - name: custom-tools
              mountPath: /custom-tools
      containers:
        - name: argocd-repo-server
          volumeMounts:
            - name: custom-tools
              mountPath: /usr/local/bin/argocd-vault-plugin
              subPath: argocd-vault-plugin
          envFrom:
            - secretRef:
                name: argocd-vault-plugin-credentials
      volumes:
        - name: custom-tools
          emptyDir: {}

Method 2: Sidecar Plugin (ArgoCD 2.6+)

apiVersion: argoproj.io/v1alpha1
kind: ConfigManagementPlugin
metadata:
  name: argocd-vault-plugin
spec:
  allowConcurrency: true
  discover:
    find:
      command: [sh, -c]
      args: ["find . -name '*.yaml' | head -1"]
  generate:
    command: [argocd-vault-plugin]
    args: [generate, ./]
  lockRepo: false

Plugin Registration

Register AVP in the argocd-cm ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cm
  namespace: argocd
data:
  configManagementPlugins: |
    - name: argocd-vault-plugin
      generate:
        command: ["argocd-vault-plugin"]
        args: ["generate", "./"]

    - name: argocd-vault-plugin-helm
      init:
        command: [sh, -c]
        args: ["helm dependency build"]
      generate:
        command: [sh, -c]
        args: ["helm template $ARGOCD_APP_NAME -n $ARGOCD_APP_NAMESPACE . | argocd-vault-plugin generate -"]

    - name: argocd-vault-plugin-kustomize
      generate:
        command: [sh, -c]
        args: ["kustomize build . | argocd-vault-plugin generate -"]

Vault Authentication for AVP

apiVersion: v1
kind: Secret
metadata:
  name: argocd-vault-plugin-credentials
  namespace: argocd
type: Opaque
stringData:
  VAULT_ADDR: "https://vault.corp.com:8200"
  AVP_TYPE: "vault"
  AVP_AUTH_TYPE: "k8s"
  AVP_K8S_MOUNT_PATH: "auth/kubernetes"
  AVP_K8S_ROLE: "argocd-avp-role"

Configure Vault:

# Create a policy for ArgoCD
vault policy write argocd-avp-policy - <<EOF
path "secret/data/*" {
  capabilities = ["read"]
}
EOF

# Create a role for the ArgoCD repo-server SA
vault write auth/kubernetes/role/argocd-avp-role \
  bound_service_account_names=argocd-repo-server \
  bound_service_account_namespaces=argocd \
  policies=argocd-avp-policy \
  ttl=1h

Option B: AppRole Auth

apiVersion: v1
kind: Secret
metadata:
  name: argocd-vault-plugin-credentials
  namespace: argocd
type: Opaque
stringData:
  VAULT_ADDR: "https://vault.corp.com:8200"
  AVP_TYPE: "vault"
  AVP_AUTH_TYPE: "approle"
  AVP_ROLE_ID: "<role-id>"
  AVP_SECRET_ID: "<secret-id>"

Option C: Token Auth (Dev/Testing Only)

stringData:
  VAULT_ADDR: "https://vault.corp.com:8200"
  AVP_TYPE: "vault"
  AVP_AUTH_TYPE: "token"
  VAULT_TOKEN: "<vault-token>"

Using Placeholders in Manifests

Basic Placeholder Syntax

apiVersion: v1
kind: Secret
metadata:
  name: webapp-secret
  annotations:
    avp.kubernetes.io/path: "secret/data/webapp/config"
type: Opaque
stringData:
  db_host: <db_host>
  db_user: <db_user>
  db_pass: <db_pass>

AVP replaces <db_host> with the value of key db_host at path secret/data/webapp/config.

Per-Key Path Override

stringData:
  db_pass: <path:secret/data/webapp/db#password>
  api_key: <path:secret/data/webapp/api#key>

Versioned Secrets (KV v2)

stringData:
  db_pass: <path:secret/data/webapp/config#db_pass#3>
  # Reads version 3 of the secret

ConfigMaps with Placeholders

apiVersion: v1
kind: ConfigMap
metadata:
  name: webapp-config
  annotations:
    avp.kubernetes.io/path: "secret/data/webapp/config"
data:
  config.yaml: |
    database:
      host: <db_host>
      port: "5432"
      name: <db_name>
    redis:
      url: <redis_url>

Helm Values with AVP

In your Git repo, create a Helm values file with placeholders:

# values.yaml
database:
  host: <path:secret/data/webapp/config#db_host>
  password: <path:secret/data/webapp/config#db_pass>

ingress:
  tls:
    secretName: <path:secret/data/webapp/tls#cert_name>

Creating the ArgoCD Application

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: webapp
  namespace: argocd
spec:
  project: default
  destination:
    server: https://kubernetes.default.svc
    namespace: webapp-ns
  source:
    repoURL: https://github.com/your-org/k8s-manifests.git
    path: apps/webapp
    plugin:
      name: argocd-vault-plugin
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

For Helm with AVP:

source:
  repoURL: https://github.com/your-org/helm-charts.git
  path: charts/webapp
  plugin:
    name: argocd-vault-plugin-helm

Refreshing Secrets

Secrets are resolved at sync time. To force a re-fetch:

# Hard refresh via CLI
argocd app get webapp --hard-refresh

# Or trigger a sync
argocd app sync webapp

For automated rotation, set up a CronJob that triggers ArgoCD syncs:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: refresh-webapp-secrets
  namespace: argocd
spec:
  schedule: "0 */4 * * *"  # Every 4 hours
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: argocd-sync
              image: argoproj/argocd:v2.12
              command: [argocd]
              args:
                - app
                - sync
                - webapp
                - --server
                - argocd-server.argocd.svc
                - --auth-token
                - $(ARGOCD_TOKEN)
          restartPolicy: OnFailure

Multi-Environment Setup

Structure your Git repo for environment isolation:

k8s-manifests/
├── base/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── kustomization.yaml
├── overlays/
│   ├── dev/
│   │   ├── secret.yaml        # avp.kubernetes.io/path: "secret/data/dev/webapp"
│   │   └── kustomization.yaml
│   ├── staging/
│   │   ├── secret.yaml        # avp.kubernetes.io/path: "secret/data/staging/webapp"
│   │   └── kustomization.yaml
│   └── prod/
│       ├── secret.yaml        # avp.kubernetes.io/path: "secret/data/prod/webapp"
│       └── kustomization.yaml

Create separate ArgoCD Applications per environment, each using argocd-vault-plugin-kustomize.

Troubleshooting

“could not find placeholder”

  • Check that avp.kubernetes.io/path annotation matches a real Vault path
  • Verify the secret exists: vault kv get secret/webapp/config

“permission denied”

  • Check that the AVP auth role has the correct policy attached
  • Verify the ArgoCD repo-server ServiceAccount is correctly bound

Plugin not executing

# Check repo-server logs
kubectl logs -n argocd deployment/argocd-repo-server

# Verify plugin binary exists
kubectl exec -n argocd deployment/argocd-repo-server -- \
  argocd-vault-plugin version

Return to the main Vault + Kubernetes guide for the complete architecture overview and auth method comparison.