Argo CD doesn’t natively inject Vault secrets; it treats them as just another Kubernetes Secret resource that your application then consumes.

Let’s see how this plays out. Imagine you have a Vault secret at secret/my-app/creds with a username and password key. You’ve configured Argo CD to sync your application, and within that application’s manifest, you have a Kubernetes Secret object defined.

apiVersion: v1
kind: Secret
metadata:
  name: my-app-creds
  namespace: default
type: Opaque
data:
  # This is where the magic (or lack thereof) happens
  # We'll fill this in later via Vault

And your application deployment looks like this:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app-container
        image: nginx:latest
        ports:
        - containerPort: 80
        env:
        - name: DB_USERNAME
          valueFrom:
            secretKeyRef:
              name: my-app-creds # References the K8s Secret
              key: username
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: my-app-creds # References the K8s Secret
              key: password

Argo CD’s job is to make sure the Deployment and the Secret exist in your cluster as defined in your Git repository. It doesn’t reach into Vault itself.

To bridge this gap, you need a component that can read from Vault and populate the Kubernetes Secret object. The most common and idiomatic way to do this is with the Vault Agent Injector (or the older Vault Agent sidecar pattern, though injection is preferred).

Here’s the mental model:

  1. Vault Server: Your secrets live here.
  2. Vault Agent Injector (running in Kubernetes): This is a Kubernetes Mutating Admission Webhook. When you create or update a Pod, it intercepts the request.
  3. Kubernetes API Server: The Injector talks to this.
  4. Argo CD: Deploys your application manifests, including the Pod/Deployment spec.
  5. Your Application Pod: The Pod, after being injected by the Vault Agent, will have a sidecar container (or the main container will be modified) that mounts a volume containing the rendered secrets.

How the Vault Agent Injector works:

You annotate your Deployment (or Pod template) to tell the Vault Agent Injector that it should intervene.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: default
  annotations:
    vault.hashicorp.com/agent-inject: "true"
    vault.hashicorp.com/role: "my-app-role" # K8s auth role for Vault
    vault.hashicorp.com/agent-inject-secret-creds.txt: "secret/my-app/creds" # Path and filename for the secret
    # vault.hashicorp.com/agent-inject-template-creds.txt: | # Optional: for templating

    #   {{- with secret "secret/my-app/creds" -}}


    #   username: {{ .Data.data.username }}


    #   password: {{ .Data.data.password }}


    #   {{- end -}}

spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app-container
        image: nginx:latest
        ports:
        - containerPort: 80
        # Env vars still point to the K8s Secret, but the K8s Secret
        # will be populated by the init container from Vault.
        env:
        - name: DB_USERNAME
          valueFrom:
            secretKeyRef:
              name: my-app-creds # This K8s Secret will be populated by the init container
              key: username
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: my-app-creds
              key: password

When Argo CD deploys this Deployment, the Kubernetes API server receives the Pod definition. The Vault Agent Injector webhook intercepts it.

  1. It sees the vault.hashicorp.com/agent-inject: "true" annotation.
  2. It consults vault.hashicorp.com/role to authenticate with Vault using Kubernetes Service Account authentication.
  3. It reads vault.hashicorp.com/agent-inject-secret-creds.txt to know that it needs to fetch secret/my-app/creds from Vault.
  4. It generates a Kubernetes Secret object named my-app-creds (derived from creds.txt) and populates it with the data from Vault. This Kubernetes Secret is created in addition to your application’s Deployment.
  5. It modifies the Pod spec to include an initContainer (or sometimes modifies the main container) that starts up, fetches secrets from Vault, and writes them to a shared emptyDir volume.
  6. It also modifies the Pod spec to mount this emptyDir volume into the main application container.

Your application container then reads the secrets from the mounted volume (e.g., /vault/secrets/creds.txt). The valueFrom.secretKeyRef in your Deployment manifest is a bit of a red herring here if you’re using the injector directly on the Pod. The injector can be configured to populate a Kubernetes Secret resource or to mount secrets directly into the Pod.

The most common pattern with Argo CD and Vault Agent Injector:

  1. Argo CD deploys your application manifests: This includes your Deployment with Vault annotations.
  2. Vault Agent Injector (Kubernetes Admission Controller) intercepts Pod creation:
    • It reads the annotations (vault.hashicorp.com/agent-inject, vault.hashicorp.com/role, vault.hashicorp.com/agent-inject-secret-*).
    • It authenticates to Vault (using the Pod’s Service Account and a Vault K8s auth role).
    • It fetches the specified secrets from Vault.
    • It creates a new Kubernetes Secret object in the cluster (e.g., my-app-creds). The name of this K8s secret is derived from the annotation key (e.g., creds.txt -> creds).
    • It also modifies the Pod spec to mount this newly created Kubernetes Secret as a volume.
  3. Your application Pod starts: It can now access the secrets either directly from the mounted volume (e.g., /vault/secrets/creds.txt) or, if you’ve configured your Deployment to reference the automatically created Kubernetes Secret (which is often named based on the annotation), it can use secretKeyRef.

The key insight is that Argo CD itself doesn’t talk to Vault. It just deploys the Deployment resource. The Vault Agent Injector, a separate Kubernetes component, is the one that performs the dynamic fetching and injection. Argo CD’s role is to ensure the Deployment resource with the correct annotations is present in the cluster.

The next step you’ll likely encounter is managing Vault auth roles and policies to grant your Kubernetes Service Accounts the necessary permissions to read specific secrets.

Want structured learning?

Take the full Argocd course →