Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Manage Secrets in GitOps with Sealed Secrets

By Kokil Thapa | Last reviewed: September 2026

You cannot store plain database passwords or API keys in a Git repository and still call your pipeline secure. GitOps demands that cluster state lives in version control, yet Kubernetes Secrets are only base64-encoded—not encrypted. To manage secrets in GitOps with Sealed Secrets, you encrypt sensitive values client-side so Git holds ciphertext only, and a cluster controller decrypts them into native Secrets at sync time. This guide walks through installation, daily workflows, comparisons with alternatives, and production patterns I've used alongside GitOps principles on real deployments.

Why do you need Sealed Secrets in a GitOps workflow?

GitOps treats Git as the single source of truth. Argo CD, Flux, or similar tools continuously reconcile cluster state from committed manifests. That model breaks the moment someone commits a literal DB_PASSWORD string. Base64 in a Kubernetes Secret is not encryption. Anyone with repo access reads production credentials in seconds.

Teams often react by keeping secrets out of Git entirely. They maintain parallel stores—CI variables, cloud vaults, or manual kubectl steps. Drift follows. The cluster no longer matches the repo. Rollbacks skip credential updates. New environments need hand-copying values nobody documented.

Sealed Secrets closes that gap. You commit encrypted SealedSecret resources. They are safe in public forks, pull-request diffs, and long-lived branches. Only the target cluster's private key decrypts them. Your Argo CD GitOps pipeline stays fully declarative without exposing plaintext.

GitOps Secrets: Plain vs SealedPlain Secret in GitBase64 onlyReadable in PR diffsCredential leak riskSealedSecret in GitAsymmetric encryptionSafe in public reposCluster-only decryptGitOps Controller Sync PathGit commit → Argo CD / Flux → Kubernetes APISealed Secrets controller creates native Secret
Manage secrets in GitOps with Sealed Secrets by replacing readable Kubernetes Secrets with encrypted SealedSecret manifests safe for version control.

On production systems I maintain, pairing Sealed Secrets with secrets scanning in Git and CI gives defence in depth. Sealed Secrets prevents accidental plaintext commits. Gitleaks catches mistakes when someone bypasses the workflow.

How does the Sealed Secrets encryption model work?

Bitnami Sealed Secrets uses asymmetric cryptography. The controller generates an RSA key pair on first startup. It exposes the public key through a cluster endpoint and keeps the private key inside the cluster—typically in a Secret mounted into the controller pod.

The kubeseal CLI fetches that public key. It encrypts your Secret data into a SealedSecret custom resource. Once sealed, ciphertext binds to namespace and name scope. You cannot rename the resource or move it to another namespace without re-sealing.

Core components you will deploy

  • Controller: Watches SealedSecret objects and emits standard Secret resources.
  • CRD: Defines the SealedSecret API at bitnami.com/v1alpha1.
  • kubeseal CLI: Local encryption tool used by developers and CI jobs.
  • cert-manager integration: Optional automatic key rotation in larger setups.
Sealed Secrets Encryption FlowDeveloperkubectl + kubesealPublic Keycluster fetchGit RepoSealedSecret YAMLGitOps SyncArgo CD / FluxSealed SecretsControllerPrivate key decryptsNative KubernetesSecretPods mount env vars
The Sealed Secrets controller holds the private key; kubeseal encrypts with the public key before manifests enter Git.

Official documentation for the project lives at the Bitnami Sealed Secrets GitHub repository. Kubernetes native Secret behaviour is documented in the Kubernetes Secrets reference.

How do you install and configure Sealed Secrets on Kubernetes?

Install the controller once per cluster—or per trust boundary if clusters must not share keys. Most teams use the upstream Helm chart or the static manifest bundle from the project releases page.

Install the controller with Helm

helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm repo update

helm install sealed-secrets sealed-secrets/sealed-secrets \
  --namespace kube-system \
  --create-namespace \
  --set fullnameOverride=sealed-secrets-controller

Verify the controller pod runs and the CRD exists:

kubectl get pods -n kube-system -l app.kubernetes.io/name=sealed-secrets
kubectl get crd sealedsecrets.bitnami.com

Install the kubeseal CLI locally

Match CLI version to controller version. Version skew often causes opaque encryption errors.

KUBESEAL_VERSION='0.27.1'
curl -OL "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz"
tar xfz kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz kubeseal
sudo install kubeseal /usr/local/bin/kubeseal

Fetch the cluster public certificate

Store the public cert in your repo for offline sealing in CI. This cert encrypts only—it cannot decrypt.

kubeseal --fetch-cert \
  --controller-name=sealed-secrets-controller \
  --controller-namespace=kube-system \
  > pub-cert.pem

For multi-cluster GitOps layouts, each environment gets its own cert file. A production SealedSecret sealed with a staging key fails on the production cluster by design. That failure mode protects you from cross-environment leaks. Patterns like this appear in multi-cluster GitOps patterns guides.

How do you create and commit sealed secrets safely?

The daily workflow stays close to standard kubectl. You write a normal Secret manifest, pipe it through kubeseal, and commit the output. Never commit the plaintext intermediate file.

Seal a secret from a literal value

  1. Create a temporary local Secret manifest—add the filename to .gitignore.
  2. Pipe it through kubeseal to produce a SealedSecret YAML.
  3. Commit only the SealedSecret into your GitOps repo path.
  4. Let Argo CD or Flux sync; confirm the native Secret exists.
kubectl create secret generic db-credentials \
  --from-literal=username=app_user \
  --from-literal=password='S3cur3P@ss!' \
  --namespace=production \
  --dry-run=client -o yaml > /tmp/db-credentials.yaml

kubeseal \
  --controller-name=sealed-secrets-controller \
  --controller-namespace=kube-system \
  --format yaml \
  < /tmp/db-credentials.yaml \
  > clusters/production/sealed/db-credentials.yaml

shred -u /tmp/db-credentials.yaml

Commit the sealed output:

git add clusters/production/sealed/db-credentials.yaml
git commit -m "Add sealed DB credentials for production"
git push origin main

Reference the resulting Secret from a Deployment as usual:

env:
  - name: DB_USER
    valueFrom:
      secretKeyRef:
        name: db-credentials
        key: username
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: db-credentials
        key: password
Sealed Secret Creation WorkflowStep 1Draft SecretStep 2Run kubesealStep 3Commit YAMLStep 4GitOps syncSecurity Checklist Before PushDelete plaintext temp filesConfirm .gitignore covers /tmp secretsRun gitleaks scan in CI pipeline
Daily GitOps workflow to manage secrets in GitOps with Sealed Secrets: draft, seal, commit, sync, verify.

Rotate a sealed secret without downtime

Update the plaintext locally, re-seal with the same name and namespace, commit, and sync. The controller updates the native Secret. Restart pods if they do not reload env vars automatically.

kubectl create secret generic api-token \
  --from-literal=token='new-token-value' \
  --namespace=production \
  --dry-run=client -o yaml | kubeseal --format yaml \
  > clusters/production/sealed/api-token.yaml

Generate strong random values before sealing using a local password generator or openssl rand -base64 32. For base64-encoded binary keys, a base64 encoder tool helps verify format before you seal.

How do Sealed Secrets compare to other GitOps secret tools?

Sealed Secrets is one option—not the only one. Pick based on team size, cloud footprint, rotation needs, and audit requirements.

ToolSecrets in GitExternal dependencyBest fit
Sealed SecretsYes—encrypted SealedSecret CRsNone beyond the cluster controllerSmall teams, self-hosted K8s, simple GitOps
Mozilla SOPSYes—encrypted YAML/JSON filesKMS, PGP, or age keysMulti-resource repos, Terraform + K8s mixed repos
External Secrets OperatorNo—references onlyVault, AWS Secrets Manager, GCP SMEnterprise audit, dynamic secrets, central vault
Cloud-native syncNoAWS/GCP/Azure secret storesHeavy cloud IAM integration

Sealed Secrets wins on simplicity. No vault cluster to operate. No cloud lock-in. Encrypted manifests diff cleanly in pull requests. The trade-off is key management inside the cluster. Backup the controller private key securely. Losing it means re-sealing every secret.

For vault-centric setups, read External Secrets Operator with Vault and HashiCorp Vault secrets management. For Ansible-heavy infra repos, Ansible Vault for secrets solves a parallel problem outside Kubernetes.

GitOps Secret Tool Decision GuideNeed secrets in Git?Self-hosted K8s→ Sealed SecretsK8s + Terraform→ SOPS + KMSCentral audit vault→ External SecretsProduction RecommendationStart with Sealed Secrets; migrate to ESO when vault ops mature
Choose Sealed Secrets for self-hosted GitOps simplicity; graduate to External Secrets when central vault audit becomes mandatory.

What are the best practices for production Sealed Secrets?

Treating Sealed Secrets as fire-and-forget creates incidents. These practices come from production GitOps work alongside CI/CD secrets management best practices.

Scope encryption per cluster and namespace

Use strict scope unless you have a documented reason for cluster-wide secrets. Strict scope binds ciphertext to both namespace and name. A sealed staging credential cannot decrypt in production even if someone commits it to the wrong folder.

kubeseal --scope strict --format yaml < secret.yaml > sealed-secret.yaml

Backup the controller private key

Export and store the sealing key in your organisation password vault. After disaster recovery on a fresh cluster, restore the key before syncing SealedSecrets. Without it, every secret needs manual re-encryption.

kubectl get secret -n kube-system \
  -l sealedsecrets.bitnami.com/sealed-secrets-key=active \
  -o yaml > sealed-secrets-master-key-backup.yaml

Encrypt that backup file itself. Never commit it to Git plain.

Integrate with Argo CD or Flux cleanly

Place SealedSecret manifests in the same Kustomize overlay or Helm chart path as the Deployments that consume them. Argo CD sync waves can order SealedSecret before Deployment if needed. See set up GitOps with Argo CD and Flux GitOps toolkit deep dive for repo layout examples.

Restrict RBAC on native Secrets

Sealed Secrets protects Git. Inside the cluster, native Secrets still need RBAC. Limit who can kubectl get secret in production namespaces. Enable encryption at rest via Kubernetes API encryption provider config for etcd.

Automate sealing in CI with care

CI jobs can seal secrets using the public cert—no cluster admin kubeconfig required. Pass plaintext via protected CI variables only. Output sealed YAML as an artefact or direct commit. Follow patterns from handle secrets in CI/CD pipelines safely.

On Laravel or API workloads deployed to Kubernetes—common on booking platforms I have shipped—application secrets like APP_KEY, payment gateway tokens, and database URLs all flow through this same sealed manifest pattern. The application code stays unchanged; only the delivery mechanism differs from a traditional .env on a single VPS.

For teams without in-house cluster ops, Linux system administration and support and maintenance services cover controller upgrades, key rotation, and GitOps pipeline hardening. Enterprise application development engagements often include this secrets layer from day one rather than retrofitting after an audit finding.

Key Takeaways

  • Sealed Secrets lets you commit encrypted credentials to Git while keeping plaintext only inside the target cluster.
  • Install one controller per cluster, match kubeseal version to controller version, and store the public cert for CI sealing.
  • Never commit plaintext Secret manifests—seal locally or in CI, then delete temp files and run gitleaks.
  • Backup the controller private key; losing it forces re-sealing every SealedSecret in the repo.
  • Use strict scope by default so ciphertext cannot decrypt in the wrong namespace or cluster.
  • Pair Sealed Secrets with RBAC, etcd encryption at rest, and secrets scanning for defence in depth.

People Also Ask

Can Sealed Secrets be decrypted outside the cluster?

No. Only the controller holding the private RSA key can decrypt a SealedSecret. The public key used by kubeseal performs one-way encryption. Even repository admins with full Git access cannot recover plaintext without cluster access and RBAC permission to read the generated native Secret.

Are Sealed Secrets safe in a public GitHub repository?

Yes, for practical purposes. The encrypted blob resists offline brute-force attacks when proper key lengths are used. Treat this as safe storage in Git, not a substitute for repo access control. Combine with branch protection, required reviews, and CI secrets scanning for a complete posture.

What happens when the Sealed Secrets controller is down?

Existing native Secrets remain in etcd—running pods keep working. New or updated SealedSecrets will not reconcile until the controller recovers. GitOps sync may report healthy while secret updates stall. Monitor controller health as a critical cluster component.

Does Sealed Secrets work with Argo CD and Flux?

Yes. Both sync SealedSecret CRs like any other manifest. The controller creates standard Secrets that Deployments reference. No special Argo CD plugin is required. Ensure the controller is installed before the first sync wave containing SealedSecrets.

Ship GitOps without leaking credentials

You can manage secrets in GitOps with Sealed Secrets today without operating a separate vault cluster. Install the controller, seal with kubeseal, commit encrypted manifests, and let your existing Argo CD or Flux pipeline reconcile them. Start strict, back up your keys, and scan Git for accidental plaintext. That combination covers most small and mid-size teams until central audit demands push you toward External Secrets.

Need help wiring Sealed Secrets into a production GitOps pipeline or migrating from manual kubectl secrets? Review the client portal work in my portfolio or read more on the Kubernetes secrets management and Flux CD vs Argo CD pages. When you want hands-on setup on your cluster, contact us for a scoped engagement.

Frequently Asked Questions

Sealed Secrets are encrypted Kubernetes custom resources that let you store credentials in Git safely. You seal plaintext with kubeseal using the cluster public key, commit the SealedSecret YAML, and the controller decrypts it into a native Secret at sync time.

GitOps treats Git as the single source of truth, so Argo CD or Flux continuously reconciles manifests from the repo. A standard Kubernetes Secret is only base64-encoded, not encrypted, so anyone with repository access can read production passwords in seconds. Teams that keep secrets out of Git entirely end up with parallel stores, cluster drift, rollbacks that skip credential updates, and hand-copied values for new environments. Sealed Secrets closes that gap by letting you commit ciphertext that only the target cluster can decrypt, keeping your pipeline fully declarative without exposing plaintext in pull requests or forks.

Bitnami Sealed Secrets uses asymmetric cryptography. On first startup the controller generates an RSA key pair, exposes the public key through a cluster endpoint, and keeps the private key inside the cluster, typically in a Secret mounted into the controller pod. The kubeseal CLI fetches that public key and encrypts your Secret data into a SealedSecret custom resource at bitnami.com/v1alpha1. Once sealed, ciphertext binds to namespace and name scope, so you cannot rename the resource or move it to another namespace without re-sealing. The controller watches SealedSecret objects and emits standard Secret resources that Deployments reference normally.

Install the controller once per cluster, or once per trust boundary if clusters must not share keys. Most teams use the upstream Helm chart from bitnami-labs.github.io/sealed-secrets or the static manifest bundle from project releases. A typical Helm install targets kube-system with fullnameOverride set to sealed-secrets-controller. Verify with kubectl get pods in kube-system labeled app.kubernetes.io/name=sealed-secrets and confirm the sealedsecrets.bitnami.com CRD exists. Install kubeseal locally and match its version to the controller version, because version skew often causes opaque encryption errors. Fetch the cluster public certificate with kubeseal --fetch-cert and store pub-cert.pem in your repo for offline sealing in CI.

Write a normal Secret manifest locally, pipe it through kubeseal, and commit only the SealedSecret output. Add the plaintext filename to .gitignore, use kubectl create secret with --dry-run=client -o yaml to generate the temp file, seal it with kubeseal --format yaml pointing at your controller name and namespace, then shred or delete the plaintext intermediate. Commit the sealed YAML into your GitOps repo path, push, and let Argo CD or Flux sync. Confirm the native Secret exists in the target namespace. Reference it from Deployments with secretKeyRef as you would any Kubernetes Secret. Never commit the plaintext intermediate file.

No. Only the controller holding the private RSA key decrypts a SealedSecret. The public key used by kubeseal performs one-way encryption only.

Yes, for practical purposes. Encrypted blobs resist offline brute-force attacks when proper key lengths are used, but treat this as safe Git storage, not a substitute for access control.

Existing native Secrets remain in etcd, so running pods keep working with their current credentials. New or updated SealedSecrets will not reconcile until the controller recovers, meaning secret rotations or new deployments that depend on fresh secrets may stall even if GitOps sync reports healthy. GitOps tools like Argo CD or Flux will apply the SealedSecret manifest, but no native Secret update occurs without the controller. Monitor controller pod health in kube-system as a critical cluster component, similar to other infrastructure controllers. Plan alerting on controller restarts and failed reconciliations so credential updates are not silently delayed during an outage.

Yes. Both Argo CD and Flux sync SealedSecret custom resources like any other manifest in your GitOps repo. No special Argo CD plugin is required. The Sealed Secrets controller must be installed and running before the first sync wave containing SealedSecrets, otherwise encrypted resources sit in the cluster without producing native Secrets. Place SealedSecret manifests in the same Kustomize overlay or Helm chart path as the Deployments that consume them. Argo CD sync waves can order SealedSecret creation before Deployment if your application needs the Secret to exist at pod startup. After sync, Deployments reference the generated Secret by name exactly as they would with a manually applied kubectl secret.

Sealed Secrets stores encrypted SealedSecret custom resources directly in Git with no external dependency beyond the cluster controller, making it ideal for small teams and self-hosted Kubernetes GitOps. External Secrets Operator keeps secrets out of Git entirely and references external stores like HashiCorp Vault, AWS Secrets Manager, or GCP Secret Manager, which suits enterprise audit requirements and dynamic secrets. Sealed Secrets wins on simplicity with no vault cluster to operate and no cloud lock-in, and encrypted manifests diff cleanly in pull requests. External Secrets wins when central vault audit, dynamic credential issuance, and heavy cloud IAM integration become mandatory. Many teams start with Sealed Secrets and graduate to External Secrets when compliance demands a central vault.

Both tools let you commit encrypted secrets to Git, but they differ in format and key management. Sealed Secrets produces Kubernetes SealedSecret custom resources sealed with the cluster RSA public key, scoped to namespace and name, and decrypted only by the in-cluster controller. Mozilla SOPS encrypts YAML or JSON files using KMS, PGP, or age keys, which works well for mixed repos containing Terraform, Ansible, and Kubernetes manifests together. Sealed Secrets fits pure Kubernetes GitOps where Argo CD or Flux reconciles cluster state. SOPS fits teams managing secrets across multiple infrastructure layers in one repository. Neither replaces the other; choose based on whether your secrets live exclusively in Kubernetes manifests or span broader infrastructure code.

Losing the private key means existing SealedSecret manifests in Git cannot decrypt on a fresh cluster. You must re-seal every secret with a new controller key pair. Export and backup the active sealing key from kube-system using kubectl get secret labeled sealedsecrets.bitnami.com/sealed-secrets-key=active, store that backup in your organisation password vault, and encrypt the backup file itself. Never commit the master key backup to Git in plaintext. After disaster recovery on a new cluster, restore the key before syncing SealedSecrets from your GitOps repo. Without that restore step, Argo CD or Flux will apply SealedSecrets that fail decryption, blocking application deployments that depend on those credentials.

Strict scope binds sealed ciphertext to both the Secret name and namespace. Run kubeseal with --scope strict when sealing so a credential encrypted for staging cannot decrypt if someone accidentally commits it to a production folder or applies it in the wrong namespace. Use strict scope by default unless you have a documented reason for cluster-wide secrets. Multi-cluster GitOps layouts should give each environment its own public cert file; a SealedSecret sealed with a staging key fails on production by design, protecting you from cross-environment credential leaks. Cluster-wide scope is available but increases blast radius if a sealed file lands in the wrong reconciliation path.

Yes. Match kubeseal CLI version to controller version during installation and in CI jobs that seal secrets. Version skew between the CLI and controller often produces opaque encryption errors that are difficult to diagnose without checking release notes. The article references kubeseal 0.27.1 as an example install. When upgrading the controller via Helm, upgrade kubeseal on developer machines and CI runners at the same time. Store the cluster public certificate in your repo so CI can seal using the cert alone without cluster admin kubeconfig access, but still keep CLI and controller versions aligned to avoid format or API mismatches against the bitnami.com/v1alpha1 CRD.

Scope encryption per cluster and namespace using strict scope by default. Backup the controller private key securely outside Git and encrypt that backup file. Integrate SealedSecret manifests into the same GitOps repo layout as consuming Deployments, using sync waves if ordering matters. Restrict RBAC on native Secrets inside the cluster because Sealed Secrets protects Git, not in-cluster access. Enable Kubernetes encryption at rest for etcd via the API encryption provider config. Never commit plaintext Secret manifests; seal locally or in CI, delete temp files, and run gitleaks for defence in depth. Automate sealing in CI using the public cert with plaintext passed only through protected CI variables. Monitor controller health and plan secret rotation by re-sealing with the same name and namespace, then restarting pods that do not reload env vars automatically.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: