Manage Secrets with SOPS and age

Khimananda Oli 9 min read Database
Manage Secrets with SOPS and age

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.

Developer Laptopage-keygen → keys.txtsops edit config.yamlEncrypts VALUES onlyGit Repositoryconfig.yaml (encrypted)Safe to commit & diffNO plaintext secretsCI/CD Pipeline$SOPS_AGE_KEY injectedsops decrypt → stdoutApply to K8s / Terraform
End-to-end workflow to manage secrets with SOPS and age: local encryption, safe Git storage, and automated CI/CD decryption.

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.

BEFORE (Plaintext)database:host: db.prod.internalpassword: SuperSecret123!port: 5432api_key: sk-live-abc123xyzreplicas: 3AFTER (SOPS + age)database:host: ENC[AES256_GCM,data:Tr8...]password: ENC[AES256_GCM,data:Kp2...]port: ENC[AES256_GCM,data:Wn5...]api_key: ENC[AES256_GCM,data:Jm9...]replicas: ENC[AES256_GCM,data:Qr1...]sops:age:- recipient: age1ql3z...enc: |-----BEGIN AGE...sops encrypt
SOPS preserves YAML structure and keys while encrypting only values, enabling meaningful git diffs and merge conflict resolution.

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:

  1. Generate a new age keypair and add the new public key to .sops.yaml under the relevant creation rule.
  2. Run sops updatekeys -r .sops.yaml prod/secrets.yaml for each affected file. SOPS adds the new recipient to the file's metadata envelope without touching the encrypted data payload.
  3. Remove the old public key from .sops.yaml and run updatekeys again to revoke access.
  4. 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.

CriteriaSOPS + ageHashiCorp VaultSealed Secrets
Best forGitOps, IaC configs, small-to-mid teamsDynamic secrets, PKI, large enterprisesKubernetes-native static secrets
Infrastructure dependencyNone (pure crypto)HA cluster, Consul/Raft, ops overheadController pod in-cluster only
Offline capabilityFull (local keys)No (requires running server)No (requires controller)
Diff/Review friendlyYes (structured encryption)No (external store)No (opaque binary)
Dynamic secret generationNoYes (DB creds, PKI certs, cloud tokens)No
Multi-cloud / non-K8sYes (Terraform, Ansible, scripts)Yes (universal API)Kubernetes only
Operational complexityLowHighMedium
Audit trailGit history + MAC integrityBuilt-in audit backendK8s 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.

Need to store secrets?Require dynamic/ephemeral credentials?NOYESKubernetes-only scope?Use HashiCorp VaultNOYESUse SOPS + ageUse Sealed SecretsGitOps, IaC, multi-cloudK8s native, no external depsDynamic DB creds, PKI, enterprise
Decision framework: choose SOPS and age for GitOps and IaC, Vault for dynamic secrets, Sealed Secrets for Kubernetes-only workflows.

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-repo or 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.yaml with appropriate path rules. Shared private keys make revocation impossible without full rotation.
  • Decrypting to disk in CI: Never run sops decrypt -i or redirect output to a file on shared runners. Use sops exec-env or 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 lint or 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.

Frequently Asked Questions

Use your package manager like apt install sops age or brew install sops age on macOS. Verify versions with sops --version and age-keygen -h to ensure compatibility with current encryption standards and avoid legacy binary issues in production environments.

Yes. Age provides local key management, eliminating AWS KMS or GCP KMS dependencies entirely for smaller teams.

Run age-keygen -o key.txt to create a private key and capture the public key from output. Store the private key securely outside version control and reference the public recipient string in your .sops.yaml configuration file for consistent team-wide secret encryption workflows.

Define creation rules specifying path regex patterns and age recipients. List multiple public keys under the age section to allow decryption by different team members while maintaining granular access control per directory or environment-specific secret files in your repository structure.

Vault offers dynamic secrets and leasing but requires infrastructure maintenance. SOPS with age suits git-native workflows where static encrypted files suffice, reducing operational overhead significantly compared to managing a highly available Vault cluster for small to medium application deployments.

Set SOPS_AGE_KEY_FILE environment variable pointing to your private key, then run sops -d secrets.yaml. Alternatively embed the key path in .sops.yaml. Never commit private keys; use agent forwarding or secure env injection during CI pipeline execution instead.

No. You must re-encrypt every file when changing recipients. Run sops updatekeys on each encrypted document after modifying .sops.yaml to add new public keys or remove compromised ones across your entire repository systematically.

SOPS preserves data structure integrity better than opaque blobs because it encrypts values individually. Git diffs remain readable for metadata keys, though encrypted values appear as ciphertext. Resolve conflicts manually then validate decryption succeeds before committing merged changes back to main.

Yes. SOPS natively handles YAML, JSON, ENV, and INI formats. Specify input type via --input-type flag if extension detection fails. Age encryption works identically across all supported formats without requiring format-specific plugins or additional configuration adjustments.

Store the age private key as a repository secret. In your workflow, write it to a temporary file, export SOPS_AGE_KEY_FILE, decrypt configs before deployment steps, and shred the key file afterward to prevent leakage in runner artifacts or logs.

Encrypted secrets become permanently unrecoverable without backup keys or secondary recipients. Always maintain offline backups of private keys and configure multiple age recipients in .sops.yaml to ensure business continuity if primary credentials are lost or compromised unexpectedly.

Yes. Add each member's age public key to the recipients list in .sops.yaml. Any listed private key holder can decrypt files independently, enabling collaborative secret management without sharing private keys or relying on centralized key distribution mechanisms.

Run sops secrets.yaml to open the decrypted content in your default editor. SOPS re-encrypts automatically upon save and validates syntax before writing. This prevents accidental plaintext commits and ensures structural integrity throughout the editing session transparently.

Yes. Age uses modern X25519 cryptography with minimal overhead, outperforming GPG significantly for bulk operations.

SOPS lacks built-in audit logging since decryption occurs locally. Implement wrapper scripts that log access events to centralized monitoring systems or rely on git history tracking combined with CI pipeline logs to reconstruct secret access patterns for compliance requirements.