An Argo CD Config Management Plugin (CMP) that replaces secret placeholders in Kubernetes manifests with values from a secrets store.
Supported secret stores:
- sops (YAML and JSON)
- Kubernetes Secret volume mounts (plain-text files on disk)
The tool reads manifests from stdin, scans them for <secret:key|modifier> placeholders, replaces them with values from the selected store, and writes the result to stdout.
- Store secrets securely alongside GitOps configuration
- Works transparently with helm and kustomize generated manifests
- Supports any text-based Kubernetes manifest format
- Automatically handles base64-encoded values (e.g.
Secret.datafields)
The recommended installation method uses the Argo CD Config Management Plugin v2 (sidecar) pattern.
A ready-to-use Kustomization example is provided that:
- Deploys the plugin as sidecar containers in
argocd-repo-server - Configures both kustomize and helm plugin variants
- Mounts a sops age key from a Kubernetes Secret
# Apply with your own age key secret already created
kubectl apply -k examples/argocd/kustomizeThe plugin image is published to ghcr.io/mmalyska/argocd-secret-replacer.
Self-contained binaries are published on every GitHub release for:
| Platform | Target |
|---|---|
| Linux x64 | linux-x64 |
| Linux x64 musl (Alpine) | linux-musl-x64 |
| Linux ARM64 | linux-arm64 |
| Linux ARM64 musl | linux-musl-arm64 |
| Windows x64 | win-x64 |
| macOS x64 | osx-x64 |
| macOS ARM64 (Apple Silicon) | osx-arm64 |
Download and place the binary in /custom-tools/ (or any directory in your PATH inside the Argo CD repo-server container).
| Variable | Purpose | Example | Required |
|---|---|---|---|
ARGOCD_ENV_SOPS_EXE |
Override the sops executable path | /custom-tools/sops |
No (sops verb only) |
ARGOCD_ENV_SOPS_SECRET_FILE |
Path to the sops-encrypted secrets file (used by the CMP plugin discovery) | secret.sec.yaml |
No (sops verb only) |
SOPS_* |
Any sops configuration variable (see sops docs) | SOPS_AGE_KEY_FILE=/sops-age/key |
Depends on sops config |
Note: The
ARGOCD_ENV_prefix is required for Argo CD 2.4+. Older versions without this prefix are not supported.
The plugin is configured as a ConfigManagementPlugin resource mounted into the repo-server sidecar. Two secret provider verbs are available:
| Verb | Flag | Description |
|---|---|---|
sops |
-f <file> |
Decrypt a sops-encrypted YAML/JSON file |
secret |
--mount <dir> |
Read from a Kubernetes Secret mounted as a directory |
The example below shows both kustomize and helm variants using each provider.
apiVersion: argoproj.io/v1alpha1
kind: ConfigManagementPlugin
metadata:
name: sops-replacer-plugin-kustomize
spec:
version: v1.0
allowConcurrency: true
discover:
find:
command:
- sh
- "-c"
- "[[ ! -z $ARGOCD_ENV_SOPS_SECRET_FILE ]] && find . -name 'kustomization.yaml' && find . -name '$ARGOCD_ENV_SOPS_SECRET_FILE'"
generate:
command:
- bash
- "-c"
- |-
kustomize build --enable-alpha-plugins . | argocd-secret-replacer sops -f "$ARGOCD_ENV_SOPS_SECRET_FILE"
lockRepo: falseapiVersion: argoproj.io/v1alpha1
kind: ConfigManagementPlugin
metadata:
name: sops-replacer-plugin-helm
spec:
version: v1.0
allowConcurrency: true
discover:
find:
command:
- sh
- "-c"
- "[[ ! -z $ARGOCD_ENV_SOPS_SECRET_FILE ]] && find . -name 'Chart.yaml' && find . -name '$ARGOCD_ENV_SOPS_SECRET_FILE'"
init:
command:
- bash
- "-c"
- helm dependency update
generate:
command:
- bash
- "-c"
- |-
helm template --include-crds --release-name "$ARGOCD_APP_NAME" --namespace "$ARGOCD_APP_NAMESPACE" --kube-version $KUBE_VERSION --api-versions $KUBE_API_VERSIONS . | argocd-secret-replacer sops -f "$ARGOCD_ENV_SOPS_SECRET_FILE"
lockRepo: falseTo use the plugin in an application, set the plugin source:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
spec:
source:
repoURL: 'https://github.com/my-org/my-repo'
path: my-app
plugin:
name: sops-replacer-plugin-kustomize
env:
- name: SOPS_SECRET_FILE
value: secret.sec.yaml
destination:
server: 'https://kubernetes.default.svc'
namespace: my-appTo use the secret verb instead, mount a Kubernetes Secret as a volume into the sidecar and point --mount at it:
generate:
command:
- bash
- "-c"
- |-
kustomize build --enable-alpha-plugins . | argocd-secret-replacer secret --mount /cluster-secretsEach key in the Kubernetes Secret becomes a file in the mounted directory, and its value is the file content.
For complete working examples see the examples directory.
The tool scans input for the following pattern:
<secret:key|modifier1|modifier2>
key— forsops: key path within the decrypted file'sdata:section; forsecret: filename in the mounted directorymodifier1,modifier2— optional output modifiers (pipe-separated)
The aliases <sops:...> and <secret:...> are both supported.
The replacer also detects base64-encoded strings (≥10 characters) and checks whether their decoded content contains placeholders. If a match is found, the replacement is performed and the result is re-encoded as base64. This means Kubernetes Secret data: fields are handled automatically — no manual |base64 modifier is needed.
| Modifier | Description |
|---|---|
base64 |
Base64-encodes the secret value |
Example:
# Input
apiVersion: v1
kind: Secret
stringData:
password: <secret:db.password>
data:
token: <secret:api.token|base64>Requirements: .NET 9 SDK
dotnet restore
dotnet build --configuration Release
dotnet testTo publish a self-contained binary:
dotnet publish src/Replacer/Replacer.csproj \
--configuration Release \
--runtime linux-x64 \
--self-contained true \
-o ./publishA Dev Container is provided using .NET 9 with sops pre-installed. Open the repository in VS Code and select Reopen in Container.
E2E tests for the
sopsverb require a valid age key configured. Unit tests andsecretverb E2E tests have no external dependencies.