
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Storing credentials in plain text within Git repositories remains one of the most common security failures I encounter during infrastructure audits. To properly manage secrets with SOPS and age, you need a workflow that encrypts values at rest while keeping configuration files version-controlled and diffable. This approach eliminates the risk of accidental credential leakage in commit history while enabling true GitOps automation without external vault dependencies for static configuration.
sops edit. This encrypts only the values (not keys) in YAML/JSON, allowing safe Git commits and automated decryption in CI/CD pipelines via the private key.How do you set up age and SOPS to manage secrets securely?
Before you can manage Kubernetes secrets securely or protect Terraform variables, you must establish a local cryptographic foundation. The tool age (pronounced "ah-gay") is a modern, simple encryption tool that replaces GPG's complexity with X25519 key pairs. SOPS (Secrets OPerationS) then uses these keys to perform structured encryption on configuration files.
Generate your age keypair
Install age and generate a keypair. Store the private key securely — this is your master decryption key.
# Install age (macOS/Homebrew example)
brew install age
# Generate keypair and save to the standard SOPS location
mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
# Set restrictive permissions immediately
chmod 600 ~/.config/sops/age/keys.txt
# Extract the public key for sharing/team config
grep "public key:" ~/.config/sops/age/keys.txt | awk '{print $NF}' The output file contains both your private key (never share this) and your public key. In production environments, especially when preparing for SOC 2 compliance evidence collection, ensure this private key is stored in a hardware token, AWS Secrets Manager, or HashiCorp Vault rather than on disk.
Create the SOPS configuration file
SOPS uses a .sops.yaml file at your repository root to determine which keys encrypt which files. This enables granular access control per environment or team.
# .sops.yaml
creation_rules:
# Production secrets require two approvers (multi-key)
- path_regex: prod/.*\.yaml$
age: >-
age1ql3z7hjy54pw8hy7w9xk8v5n0qj3r7m2c4g6f8d2s1a0e9u7t5y3x,
age1adminbackupkey9xk8v5n0qj3r7m2c4g6f8d2s1a0e9u7t5y
# Staging uses a single team key
- path_regex: staging/.*\.yaml$
age: age1stagingteamkey7w9xk8v5n0qj3r7m2c4g6f8d2s1a0e9u7t5
# Default rule for everything else
- path_regex: .*\.yaml$
age: age1defaultdevkeyjy54pw8hy7w9xk8v5n0qj3r7m2c4g6f8d2s1a This path-based routing is critical. A common mistake I see in Nepal-based startups scaling to global clients is using a single key for all environments. When an engineer leaves, rotating that single key requires re-encrypting every secret in the repo. Environment-specific keys limit blast radius and simplify rotation.
How does SOPS encrypt YAML and JSON files without breaking diffs?
Unlike full-file encryption tools, SOPS performs structured encryption. It traverses the document tree and encrypts only the leaf values, leaving keys and structure intact. This is what makes it viable for GitOps workflows like those described in our ArgoCD GitOps setup guide.
When you run sops edit config.yaml, SOPS opens your default editor with decrypted content. Upon saving, it re-encrypts automatically. The resulting file retains its schema, meaning git diff shows exactly which fields changed (by their encrypted ciphertext), even if you cannot read the new value. This structural awareness also allows SOPS to validate integrity via MAC (Message Authentication Code) embedded in the file metadata, detecting tampering before decryption.
Essential SOPS commands for daily operations
sops edit file.yaml— Decrypt, open in $EDITOR, re-encrypt on save. Safest for modifications.sops encrypt --in-place file.yaml— Encrypt an existing plaintext file. Use once during migration.sops decrypt file.yaml— Output decrypted content to stdout. Never redirect to disk in production.sops updatekeys file.yaml— Re-encrypt data keys with updated recipients from.sops.yaml. Critical after key rotation.sops exec-env file.yaml 'command'— Inject decrypted values as environment variables into a subprocess without writing to disk.
How do you integrate SOPS and age into CI/CD pipelines safely?
Automation is where most teams fail. The private key must be available to the pipeline but never persisted to disk or logged. Whether you use GitHub Actions, GitLab CI, or Jenkins, the pattern remains consistent: inject the key as a masked secret variable and decrypt in-memory.
GitHub Actions example with OIDC
For AWS-integrated workflows, combine SOPS with OIDC to avoid long-lived credentials entirely. For pure SOPS decryption:
name: Deploy with SOPS Secrets
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install SOPS
run: |
curl -LO https://github.com/getsops/sops/releases/download/v3.9.4/sops-v3.9.4.linux.amd64
sudo mv sops-v3.9.4.linux.amd64 /usr/local/bin/sops
sudo chmod +x /usr/local/bin/sops
- name: Decrypt and apply to Kubernetes
env:
SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_PRIVATE_KEY }}
run: |
sops exec-env k8s/prod/secrets.yaml 'kubectl apply -f -'
# Or for Helm:
# sops exec-env helm upgrade --install app ./chart -f secrets.yaml Note the use of SOPS_AGE_KEY environment variable. SOPS checks this variable before falling back to the default key file path. This eliminates the need to write the private key to the runner's filesystem, reducing exposure window to zero. If you are managing multiple clusters, consider the patterns in our Amazon EKS practical guide for namespace-scoped secret injection.
Key rotation without downtime
When an engineer departs or a key is potentially compromised, rotate without re-encrypting manually:
- Generate a new age keypair and add the new public key to
.sops.yamlunder the relevant creation rule. - Run
sops updatekeys -r .sops.yaml prod/secrets.yamlfor each affected file. SOPS adds the new recipient to the file's metadata envelope without touching the encrypted data payload. - Remove the old public key from
.sops.yamland runupdatekeysagain to revoke access. - Rotate the actual underlying secrets (database passwords, API tokens) as a separate step — key rotation does not change the encrypted values themselves.
SOPS with age vs Vault vs Sealed Secrets: Which should you choose?
No single tool fits every architecture. Understanding trade-offs prevents costly migrations later. This comparison reflects real-world deployments across AWS, Azure, and on-prem environments in 2026.
| Criteria | SOPS + age | HashiCorp Vault | Sealed Secrets |
|---|---|---|---|
| Best for | GitOps, IaC configs, small-to-mid teams | Dynamic secrets, PKI, large enterprises | Kubernetes-native static secrets |
| Infrastructure dependency | None (pure crypto) | HA cluster, Consul/Raft, ops overhead | Controller pod in-cluster only |
| Offline capability | Full (local keys) | No (requires running server) | No (requires controller) |
| Diff/Review friendly | Yes (structured encryption) | No (external store) | No (opaque binary) |
| Dynamic secret generation | No | Yes (DB creds, PKI certs, cloud tokens) | No |
| Multi-cloud / non-K8s | Yes (Terraform, Ansible, scripts) | Yes (universal API) | Kubernetes only |
| Operational complexity | Low | High | Medium |
| Audit trail | Git history + MAC integrity | Built-in audit backend | K8s events only |
In practice, many organizations I advise use a hybrid: SOPS for infrastructure-as-code and application configuration, Vault for dynamic database credentials and PKI, and Sealed Secrets for developer self-service in non-production clusters. If your primary pain point is accidentally committed credentials in Terraform or Helm charts, start with SOPS. If you need ephemeral database credentials that auto-expire, go directly to Vault.
What are the common pitfalls when adopting SOPS in production?
After helping dozens of teams adopt encrypted secrets, these issues recur consistently. Avoid them proactively.
- Committing plaintext during initial migration: Always encrypt files before adding them to Git. Use
git filter-repoor BFG Repo-Cleaner if plaintext was ever committed — simply deleting and re-committing does not remove it from history. - Ignoring MAC validation failures: If SOPS reports a MAC mismatch, treat it as potential tampering until proven otherwise. Do not blindly re-encrypt to "fix" it. Investigate the diff first.
- Sharing private keys across team members: Each engineer should have their own age keypair. Add individual public keys to
.sops.yamlwith appropriate path rules. Shared private keys make revocation impossible without full rotation. - Decrypting to disk in CI: Never run
sops decrypt -ior redirect output to a file on shared runners. Usesops exec-envor pipe directly to the consuming tool (kubectl apply -f -). Ephemeral memory beats persistent storage. - Forgetting to encrypt new files: Add a pre-commit hook using
sops lintor a custom script that verifies all YAML/JSON files matching your path regex are valid SOPS-encrypted files. Catch mistakes before they reach the remote.
Secure Your Configuration Workflow Today
Learning to manage secrets with SOPS and age transforms your infrastructure from a liability into an auditable, version-controlled asset. Start by generating your first age keypair and encrypting a single non-production configuration file today. Once comfortable, extend to CI/CD integration and establish key rotation procedures before your next compliance review. If your team needs hands-on guidance implementing encrypted GitOps workflows or preparing for SOC 2 audits with SOPS-based evidence collection, reach out to discuss your specific infrastructure requirements.