<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged cluster</title>
<link>https://coderlegion.com/tag/cluster</link>
<description>Powered by Question2Answer</description>
<item>
<title>Kubernetes Security Best Practices: A Production Hardening Guide</title>
<link>https://coderlegion.com/15640/kubernetes-security-best-practices-a-production-hardening-guide</link>
<description>
&lt;a href=&quot;https://alexandre-vazquez.com/kubernetes-security-best-practices/&quot; title=&quot;Kubernetes Security Best Practices: A Production Hardening Guide&quot; rel=&quot;nofollow&quot;&gt;&lt;img width=&quot;1672&quot; height=&quot;941&quot; src=&quot;https://mlk5tkvgfeoa.i.optimole.com/cb:s8WW.68/w:1672/h:941/q:mauto/f:best/https://alexandre-vazquez.com/wp-content/uploads/2026/04/SecurityBestPractices.avif&quot; class=&quot;webfeedsFeaturedVisual wp-post-image&quot; alt=&quot;Kubernetes Security Best Practices: A Production Hardening Guide&quot; style=&quot;display: block; margin: auto; margin-bottom: 5px;max-width: 100%;&quot; title=&quot;Kubernetes Security Best Practices: A Production Hardening Guide 2&quot;&gt;&lt;/a&gt;
&lt;p class=&quot;lead&quot;&gt;Kubernetes security is not a single feature you enable &amp;mdash; it is a layered discipline that spans the control plane, workloads, networking, supply chain, and runtime. This guide covers the security controls that matter most in production, why each one exists, and how to implement them without breaking your cluster.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;The Kubernetes Attack Surface&lt;/h2&gt;



&lt;p&gt;Before hardening anything, understand what you are protecting. A Kubernetes cluster has several distinct attack surfaces:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;strong&gt;API server&lt;/strong&gt; &amp;mdash; The central control plane. Any entity that can reach it with valid credentials can read cluster state, modify workloads, or escalate privileges.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;etcd&lt;/strong&gt; &amp;mdash; Stores all cluster state in plain text, including Secrets. Direct etcd access is equivalent to root on every node.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Nodes&lt;/strong&gt; &amp;mdash; A compromised node can access all Secrets mounted on pods running on it, access the kubelet API, and potentially escape to the hypervisor.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Pods&lt;/strong&gt; &amp;mdash; Privileged pods, host-network pods, and pods with excessive capabilities can break container isolation.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Supply chain&lt;/strong&gt; &amp;mdash; Malicious images, compromised registries, and unsigned artifacts can introduce attacker-controlled code into your cluster.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;RBAC&lt;/strong&gt; &amp;mdash; Overly permissive roles allow lateral movement and privilege escalation once an attacker gains any foothold.&lt;/li&gt;&lt;/ul&gt;



&lt;p&gt;The controls below address each of these surfaces. Prioritize based on your threat model &amp;mdash; a public-facing multi-tenant cluster needs all of them; an internal development cluster can relax some.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;1. RBAC: Least Privilege from Day One&lt;/h2&gt;



&lt;p&gt;Role-Based Access Control is Kubernetes&amp;rsquo; primary authorization mechanism. Most clusters fail at RBAC not because it is misconfigured, but because it is over-permissive by default and nobody reviews it systematically.&lt;/p&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;Common RBAC Mistakes&lt;/h3&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;strong&gt;Binding to &lt;code&gt;cluster-admin&lt;/code&gt; for convenience.&lt;/strong&gt; Almost no workload needs cluster-admin. Use namespaced roles wherever possible.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Using &lt;code&gt;*&lt;/code&gt; verbs or resources in roles.&lt;/strong&gt; Wildcard permissions are almost always broader than intended.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Not auditing ServiceAccount token usage.&lt;/strong&gt; Every pod gets a ServiceAccount. The default ServiceAccount in most namespaces has no permissions, but custom workloads often get over-permissive SAs.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Forgetting &lt;code&gt;automountServiceAccountToken: false&lt;/code&gt;.&lt;/strong&gt; If a workload does not need to talk to the Kubernetes API, disable token mounting entirely.&lt;/li&gt;&lt;/ul&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;Practical RBAC Patterns&lt;/h3&gt;



&lt;p&gt;For a workload that only needs to read ConfigMaps in its own namespace:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: configmap-reader
  namespace: my-app
rules:
- apiGroups: [&quot;&quot;]
  resources: [&quot;configmaps&quot;]
  verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: my-app-configmap-reader
  namespace: my-app
subjects:
- kind: ServiceAccount
  name: my-app
  namespace: my-app
roleRef:
  kind: Role
  name: configmap-reader
  apiGroup: rbac.authorization.k8s.io&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Audit existing RBAC with &lt;a href=&quot;https://github.com/aquasecurity/kubectl-who-can&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;kubectl-who-can&lt;/a&gt; or &lt;a href=&quot;https://github.com/alcideio/rbac-tool&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;rbac-tool&lt;/a&gt; to find overly permissive bindings before attackers do.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;2. Pod Security Standards&lt;/h2&gt;



&lt;p&gt;PodSecurityPolicy was deprecated in Kubernetes 1.21 and removed in 1.25. Its replacement is &lt;strong&gt;Pod Security Admission (PSA)&lt;/strong&gt;, a built-in admission controller that enforces one of three security profiles at the namespace level:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;strong&gt;Privileged&lt;/strong&gt; &amp;mdash; No restrictions. For system components only.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Baseline&lt;/strong&gt; &amp;mdash; Prevents the most critical privilege escalations: privileged containers, hostPID, hostIPC, hostNetwork, dangerous capabilities.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Restricted&lt;/strong&gt; &amp;mdash; Enforces current hardening best practices. Requires running as non-root, dropping all capabilities, and using a restricted seccomp profile.&lt;/li&gt;&lt;/ul&gt;



&lt;p&gt;Enable enforcement at the namespace level with labels:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: v1.30
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: v1.30
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/audit-version: v1.30&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;A pod that runs as root or requests host-network in a namespace enforcing &lt;code&gt;restricted&lt;/code&gt; will be rejected at admission. The &lt;code&gt;warn&lt;/code&gt; and &lt;code&gt;audit&lt;/code&gt; modes let you test before enforcing.&lt;/p&gt;



&lt;p&gt;PSA covers the most critical pod-level escalations, but it is coarse-grained. For fine-grained policy control, use &lt;a rel=&quot;nofollow&quot; href=&quot;https://alexandre-vazquez.com/kyverno-a-detailed-way-of-enforcing-standard-and-custom-policies/&quot;&gt;Kyverno&lt;/a&gt; alongside PSA.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;3. Network Policies: Micro-Segmentation&lt;/h2&gt;



&lt;p&gt;By default, every pod in a Kubernetes cluster can communicate with every other pod across all namespaces. This is a flat network model that gives attackers unrestricted lateral movement once they compromise any workload.&lt;/p&gt;



&lt;p&gt;Network Policies define L3/L4 allow-rules for pod-to-pod communication. They are enforced by your CNI plugin (Calico, Cilium, Weave &amp;mdash; not Flannel, which does not support NetworkPolicy).&lt;/p&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;Default Deny Pattern&lt;/h3&gt;



&lt;p&gt;Start by denying all ingress and egress in every namespace, then open only what is explicitly needed:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Then allow specific traffic:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-api-to-db
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: postgres
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: api
    ports:
    - protocol: TCP
      port: 5432&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Do not forget DNS egress &amp;mdash; most workloads need to resolve names via kube-dns, which requires UDP port 53 egress to the &lt;code&gt;kube-system&lt;/code&gt; namespace.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;4. Secrets Management&lt;/h2&gt;



&lt;p&gt;Kubernetes Secrets are base64-encoded, not encrypted. Stored in etcd in plain text by default. Anyone with &lt;code&gt;get&lt;/code&gt; permission on Secrets can read them. This is not a vulnerability &amp;mdash; it is a design decision that puts the responsibility on you to:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;strong&gt;Enable encryption at rest for etcd.&lt;/strong&gt; Configure &lt;code&gt;EncryptionConfiguration&lt;/code&gt; with an AES-CBC or AES-GCM provider. This encrypts Secrets before they are written to etcd.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Use external secret stores.&lt;/strong&gt; HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault with the External Secrets Operator means actual secret values never live in Kubernetes at all.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Restrict Secret RBAC aggressively.&lt;/strong&gt; Never give &lt;code&gt;list&lt;/code&gt; on Secrets cluster-wide &amp;mdash; it returns all values. Use &lt;code&gt;get&lt;/code&gt; on named resources where possible.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Avoid environment variables for secrets.&lt;/strong&gt; Prefer volume mounts. Env vars are visible in pod inspect output and can leak through application logging.&lt;/li&gt;&lt;/ul&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;# etcd encryption at rest - in kube-apiserver config
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
  - secrets
  providers:
  - aescbc:
      keys:
      - name: key1
        secret: &amp;lt;base64-encoded-32-byte-key&amp;gt;
  - identity: {}&lt;/code&gt;&lt;/pre&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;5. Image Security and Supply Chain&lt;/h2&gt;



&lt;p&gt;Your runtime security posture is only as good as the images you run. A compromised image from a public registry bypasses every runtime control you have.&lt;/p&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;Scan images in CI&lt;/h3&gt;



&lt;p&gt;Use Trivy, Grype, or Snyk to scan images as part of your CI pipeline. Block deployments of images with critical CVEs:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;# In your CI pipeline
trivy image --exit-code 1 --severity CRITICAL your-image:tag&lt;/code&gt;&lt;/pre&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;Use a private registry with admission control&lt;/h3&gt;



&lt;p&gt;Only allow images from your private registry using an admission webhook (Kyverno, OPA Gatekeeper). This prevents developers from running arbitrary public images in production:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: restrict-image-registries
spec:
  validationFailureAction: Enforce
  rules:
  - name: validate-registries
    match:
      any:
      - resources:
          kinds: [&quot;Pod&quot;]
    validate:
      message: &quot;Images must come from registry.company.com&quot;
      pattern:
        spec:
          containers:
          - image: &quot;registry.company.com/*&quot;&lt;/code&gt;&lt;/pre&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;Use distroless or minimal base images&lt;/h3&gt;



&lt;p&gt;Distroless images contain only the application and its runtime dependencies &amp;mdash; no shell, no package manager, no debugging tools. This drastically reduces the attack surface and the number of CVEs. Google&amp;rsquo;s distroless images are available for Java, Node.js, Python, and Go.&lt;/p&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;Sign and verify images&lt;/h3&gt;



&lt;p&gt;Cosign (from the Sigstore project) lets you sign container images and verify signatures at admission time using Kyverno or Connaisseur. This prevents image substitution attacks where an attacker replaces a legitimate image in your registry.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;6. Runtime Security&lt;/h2&gt;



&lt;p&gt;Runtime security detects and responds to malicious activity after a container is running. The primary tool in this space is &lt;strong&gt;Falco&lt;/strong&gt; &amp;mdash; a CNCF project that uses eBPF to monitor system calls and raise alerts when containers behave unexpectedly.&lt;/p&gt;



&lt;p&gt;Default Falco rules catch common attack patterns:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;Shell spawned in a container&lt;/li&gt;&lt;li&gt;Network connection to an unexpected IP&lt;/li&gt;&lt;li&gt;Write to a sensitive file path (&lt;code&gt;/etc/passwd&lt;/code&gt;, &lt;code&gt;/etc/shadow&lt;/code&gt;)&lt;/li&gt;&lt;li&gt;Privilege escalation via setuid binaries&lt;/li&gt;&lt;li&gt;Container drift (new executable files written at runtime)&lt;/li&gt;&lt;/ul&gt;



&lt;p&gt;Combine Falco with &lt;strong&gt;seccomp profiles&lt;/strong&gt; to restrict the system calls a container can make at the kernel level. The &lt;code&gt;RuntimeDefault&lt;/code&gt; seccomp profile (available since Kubernetes 1.27 as a default) blocks 300+ system calls that containers virtually never need.&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      runAsNonRoot: true
      runAsUser: 65534
      capabilities:
        drop: [&quot;ALL&quot;]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;These four &lt;code&gt;securityContext&lt;/code&gt; settings together (&lt;code&gt;allowPrivilegeEscalation: false&lt;/code&gt;, &lt;code&gt;readOnlyRootFilesystem: true&lt;/code&gt;, &lt;code&gt;runAsNonRoot: true&lt;/code&gt;, &lt;code&gt;capabilities.drop: ALL&lt;/code&gt;) make container escape significantly harder and satisfy the Kubernetes Restricted pod security standard.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;7. API Server Hardening&lt;/h2&gt;



&lt;p&gt;The API server is the most critical component to harden. Key settings:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;strong&gt;Disable anonymous authentication.&lt;/strong&gt; &lt;code&gt;--anonymous-auth=false&lt;/code&gt; ensures every request is authenticated.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Enable audit logging.&lt;/strong&gt; Log all API server requests to a file or webhook. Without audit logs, you cannot investigate incidents or detect RBAC abuse.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Restrict admission plugins.&lt;/strong&gt; Ensure &lt;code&gt;NodeRestriction&lt;/code&gt; is enabled &amp;mdash; it prevents node kubelets from modifying objects outside their own node.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Do not expose the API server to the internet.&lt;/strong&gt; Use a VPN, bastion host, or private endpoint. If you must expose it, restrict access by IP.&lt;/li&gt;&lt;/ul&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;# Minimal audit policy - log all requests at metadata level,
# and full request body for sensitive resources
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: RequestResponse
  resources:
  - group: &quot;&quot;
    resources: [&quot;secrets&quot;, &quot;configmaps&quot;]
- level: Metadata
  omitStages: [&quot;RequestReceived&quot;]&lt;/code&gt;&lt;/pre&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;8. etcd Security&lt;/h2&gt;



&lt;p&gt;etcd stores all cluster state. Treat it as sensitive as your production database:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;strong&gt;Enable TLS for all etcd communication.&lt;/strong&gt; Both peer communication (etcd-to-etcd) and client communication (apiserver-to-etcd) must use mutual TLS.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Restrict network access to etcd.&lt;/strong&gt; etcd should only be reachable by the API server. Use firewall rules or security groups to enforce this.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Enable encryption at rest.&lt;/strong&gt; As described in the Secrets section above.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Backup etcd regularly.&lt;/strong&gt; An etcd snapshot is a complete copy of all cluster state, including all Secrets. Encrypt backups and store them separately from the cluster.&lt;/li&gt;&lt;/ul&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;9. CIS Kubernetes Benchmark&lt;/h2&gt;



&lt;p&gt;The &lt;strong&gt;CIS Kubernetes Benchmark&lt;/strong&gt; is a comprehensive checklist of security controls covering the control plane, nodes, and workloads. Running &lt;a href=&quot;https://github.com/aquasecurity/kube-bench&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;kube-bench&lt;/a&gt; against your cluster gives you a scored assessment against the CIS controls:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;kubectl apply -f &lt;a href=&quot;https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml&quot; rel=&quot;nofollow&quot;&gt;https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml&lt;/a&gt;
kubectl logs $(kubectl get pods -l app=kube-bench -o name)&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;kube-bench outputs PASS/FAIL/WARN for each control with remediation guidance. Run it after initial cluster setup and after major configuration changes.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;10. Continuous Security Posture with Kubescape&lt;/h2&gt;



&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://alexandre-vazquez.com/kubesec-security-kubernetes/&quot;&gt;Kubescape&lt;/a&gt; and similar tools (Starboard/Trivy Operator, KubeScore) provide continuous security scanning of live cluster state &amp;mdash; not just a one-time audit. They check workloads against NSA/CISA hardening guidelines, MITRE ATT&amp;amp;CK framework, and the CIS benchmark in real time.&lt;/p&gt;



&lt;p&gt;Deploy Trivy Operator for continuous in-cluster scanning:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;helm repo add aquasecurity &lt;a href=&quot;https://aquasecurity.github.io/helm-charts/&quot; rel=&quot;nofollow&quot;&gt;https://aquasecurity.github.io/helm-charts/&lt;/a&gt;
helm install trivy-operator aquasecurity/trivy-operator 
  --namespace trivy-system 
  --create-namespace 
  --set=&quot;trivy.ignoreUnfixed=true&quot;&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Trivy Operator creates &lt;code&gt;VulnerabilityReport&lt;/code&gt;, &lt;code&gt;ConfigAuditReport&lt;/code&gt;, and &lt;code&gt;RbacAssessmentReport&lt;/code&gt; custom resources in the same namespace as each workload. These can be scraped by Prometheus and displayed in Grafana for a security dashboard.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Security Hardening Checklist&lt;/h2&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt; RBAC reviewed &amp;mdash; no wildcard roles, no unnecessary cluster-admin bindings&lt;/li&gt;&lt;li&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt; ServiceAccount token automount disabled for workloads that do not need API access&lt;/li&gt;&lt;li&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt; Pod Security Standards enforced at namespace level (at least Baseline, Restricted where possible)&lt;/li&gt;&lt;li&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt; Network policies deployed &amp;mdash; default deny with explicit allows&lt;/li&gt;&lt;li&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt; Secrets encrypted at rest in etcd&lt;/li&gt;&lt;li&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt; Images scanned in CI &amp;mdash; no critical CVEs in production&lt;/li&gt;&lt;li&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt; Private registry enforced via admission control&lt;/li&gt;&lt;li&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt; Container securityContext hardened (non-root, read-only filesystem, no capabilities)&lt;/li&gt;&lt;li&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt; seccomp RuntimeDefault profile enabled&lt;/li&gt;&lt;li&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt; API server audit logging enabled&lt;/li&gt;&lt;li&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt; etcd TLS and network access restricted&lt;/li&gt;&lt;li&gt;&lt;img src=&quot;https://s.w.org/images/core/emoji/16.0.1/72x72/2705.png&quot; alt=&quot;&amp;#9989;&quot; class=&quot;wp-smiley&quot; style=&quot;height: 1em; max-height: 1em;&quot;&gt; kube-bench run and critical/high findings remediated&lt;/li&gt;&lt;li&gt;&amp;lt;img src=&quot;h&lt;/li&gt;&lt;/ul&gt;
</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/15640/kubernetes-security-best-practices-a-production-hardening-guide</guid>
<pubDate>Sun, 26 Apr 2026 03:43:52 +0000</pubDate>
</item>
<item>
<title>ArgoCD Guide: GitOps Continuous Delivery for Kubernetes</title>
<link>https://coderlegion.com/15641/argocd-guide-gitops-continuous-delivery-for-kubernetes</link>
<description>
&lt;a href=&quot;https://alexandre-vazquez.com/argocd-guide/&quot; title=&quot;ArgoCD Guide: GitOps Continuous Delivery for Kubernetes&quot; rel=&quot;nofollow&quot;&gt;&lt;img width=&quot;1672&quot; height=&quot;941&quot; src=&quot;https://mlk5tkvgfeoa.i.optimole.com/cb:s8WW.68/w:1672/h:941/q:mauto/f:best/https://alexandre-vazquez.com/wp-content/uploads/2026/04/ArgoCD.avif&quot; class=&quot;webfeedsFeaturedVisual wp-post-image&quot; alt=&quot;ArgoCD Guide: GitOps Continuous Delivery for Kubernetes&quot; style=&quot;display: block; margin: auto; margin-bottom: 5px;max-width: 100%;&quot; title=&quot;ArgoCD Guide: GitOps Continuous Delivery for Kubernetes 4&quot;&gt;&lt;/a&gt;
&lt;p class=&quot;lead&quot;&gt;ArgoCD has become the de facto standard for GitOps-based continuous delivery in Kubernetes. If you are running production workloads on Kubernetes and still deploying with raw &lt;code&gt;kubectl apply&lt;/code&gt; or untracked Helm releases, ArgoCD solves a class of problems you may not even know you have yet. This guide covers everything from core concepts to production-grade configuration.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;The Problem ArgoCD Solves&lt;/h2&gt;



&lt;p&gt;Traditional CI/CD pushes deployments into a cluster. A CI system runs tests, builds an image, and then executes &lt;code&gt;kubectl apply&lt;/code&gt; or &lt;code&gt;helm upgrade&lt;/code&gt; against the cluster. This model has several structural problems:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;strong&gt;Drift goes undetected.&lt;/strong&gt; Someone applies a hotfix directly to the cluster. Now your Git repository no longer reflects reality, and nobody knows it.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;No single source of truth.&lt;/strong&gt; The cluster state is authoritative, not Git. Your desired state and actual state can diverge silently.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Rollback is painful.&lt;/strong&gt; Rolling back a bad deployment means re-running old CI pipelines or manually reversing changes, neither of which is fast.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Multi-cluster management compounds the problem.&lt;/strong&gt; Each cluster becomes a snowflake with its own history of undocumented changes.&lt;/li&gt;&lt;/ul&gt;



&lt;p&gt;GitOps inverts this model. Git is the source of truth. The cluster pulls its desired state from Git and continuously reconciles toward it. ArgoCD is the most mature GitOps operator for Kubernetes, implementing this pull-based model with a production-ready feature set.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;How ArgoCD Works: Core Architecture&lt;/h2&gt;



&lt;p&gt;ArgoCD runs as a set of controllers inside your Kubernetes cluster. The core components are:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;strong&gt;Application Controller&lt;/strong&gt; &amp;mdash; Watches both the Git repository and the live cluster state. Computes the diff and drives reconciliation.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;API Server&lt;/strong&gt; &amp;mdash; Exposes the gRPC/REST API consumed by the CLI, UI, and external systems.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Repository Server&lt;/strong&gt; &amp;mdash; Generates Kubernetes manifests from source (Helm, Kustomize, plain YAML, Jsonnet).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Redis&lt;/strong&gt; &amp;mdash; Caches cluster state and repository data to reduce API server load.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Dex&lt;/strong&gt; (optional) &amp;mdash; Provides OIDC authentication for SSO integration.&lt;/li&gt;&lt;/ul&gt;



&lt;p&gt;The fundamental unit in ArgoCD is an &lt;strong&gt;Application&lt;/strong&gt; &amp;mdash; a CRD that maps a source (a path in a Git repo at a specific revision) to a destination (a namespace in a cluster). ArgoCD continuously compares the desired state from Git with the live state in the cluster and reports on the sync status.&lt;/p&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;Sync Status vs Health Status&lt;/h3&gt;



&lt;p&gt;Two orthogonal concepts you need to understand from day one:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;strong&gt;Sync Status&lt;/strong&gt; &amp;mdash; Does the live state match what Git says it should be? Values: Synced, OutOfSync, Unknown.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Health Status&lt;/strong&gt; &amp;mdash; Is the application actually working? Values: Healthy, Progressing, Degraded, Suspended, Missing, Unknown.&lt;/li&gt;&lt;/ul&gt;



&lt;p&gt;An application can be &lt;em&gt;Synced&lt;/em&gt; but &lt;em&gt;Degraded&lt;/em&gt; &amp;mdash; the manifests were applied correctly, but a pod is crash-looping. Conversely, it can be &lt;em&gt;OutOfSync&lt;/em&gt; but &lt;em&gt;Healthy&lt;/em&gt; &amp;mdash; someone applied a change directly to the cluster outside of Git.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Installing ArgoCD&lt;/h2&gt;



&lt;p&gt;The official installation method uses a single manifest. For production, always pin to a specific version:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;kubectl create namespace argocd
kubectl apply -n argocd -f &lt;a href=&quot;https://raw.githubusercontent.com/argoproj/argo-cd/v2.11.0/manifests/install.yaml&quot; rel=&quot;nofollow&quot;&gt;https://raw.githubusercontent.com/argoproj/argo-cd/v2.11.0/manifests/install.yaml&lt;/a&gt;&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This deploys ArgoCD in the &lt;code&gt;argocd&lt;/code&gt; namespace with full cluster-admin access. For a production HA setup, use the &lt;code&gt;manifests/ha/install.yaml&lt;/code&gt; variant, which deploys multiple replicas of the API server and application controller.&lt;/p&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;Accessing the UI and CLI&lt;/h3&gt;



&lt;p&gt;The initial admin password is auto-generated and stored in a secret:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;argocd admin initial-password -n argocd&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;For local access, port-forward the API server:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;kubectl port-forward svc/argocd-server -n argocd 8080:443&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Then log in via the CLI:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;argocd login localhost:8080 --username admin --password &amp;lt;password&amp;gt; --insecure&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;For production, expose the ArgoCD server via an Ingress or LoadBalancer with a proper TLS certificate. If you&amp;rsquo;re using NGINX Ingress Controller:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: argocd-server-ingress
  namespace: argocd
  annotations:
    nginx.ingress.kubernetes.io/ssl-passthrough: &quot;true&quot;
    nginx.ingress.kubernetes.io/backend-protocol: &quot;HTTPS&quot;
spec:
  ingressClassName: nginx
  rules:
  - host: argocd.yourdomain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: argocd-server
            port:
              number: 443&lt;/code&gt;&lt;/pre&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Defining Your First Application&lt;/h2&gt;



&lt;p&gt;Applications can be created via the UI, the CLI, or declaratively with a YAML manifest. The declarative approach is the recommended one &amp;mdash; it means your ArgoCD configuration itself is in Git:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: &lt;a href=&quot;https://github.com/your-org/your-app&quot; rel=&quot;nofollow&quot;&gt;https://github.com/your-org/your-app&lt;/a&gt;
    targetRevision: HEAD
    path: k8s/overlays/production
  destination:
    server: &lt;a href=&quot;https://kubernetes.default.svc&quot; rel=&quot;nofollow&quot;&gt;https://kubernetes.default.svc&lt;/a&gt;
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
    - CreateNamespace=true&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Key fields to understand:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;strong&gt;targetRevision&lt;/strong&gt; &amp;mdash; Can be a branch name, tag, or commit SHA. For production, pin to a tag rather than &lt;code&gt;HEAD&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;path&lt;/strong&gt; &amp;mdash; The directory within the repo containing your Kubernetes manifests.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;automated.prune&lt;/strong&gt; &amp;mdash; Automatically delete resources that are no longer in Git. Required for true GitOps but use carefully &amp;mdash; it will delete things.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;automated.selfHeal&lt;/strong&gt; &amp;mdash; Automatically revert manual changes made directly to the cluster. This is what enforces Git as the single source of truth.&lt;/li&gt;&lt;/ul&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Helm Integration&lt;/h2&gt;



&lt;p&gt;ArgoCD has native Helm support. It can deploy Helm charts directly from chart repositories or from your Git repository. You can override values per environment:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: prometheus-stack
  namespace: argocd
spec:
  project: default
  source:
    repoURL: &lt;a href=&quot;https://prometheus-community.github.io/helm-charts&quot; rel=&quot;nofollow&quot;&gt;https://prometheus-community.github.io/helm-charts&lt;/a&gt;
    chart: kube-prometheus-stack
    targetRevision: 58.4.0
    helm:
      releaseName: prometheus-stack
      valuesObject:
        grafana:
          adminPassword: &quot;${GRAFANA_PASSWORD}&quot;
        prometheus:
          prometheusSpec:
            retention: 30d
            storageSpec:
              volumeClaimTemplate:
                spec:
                  storageClassName: fast-ssd
                  resources:
                    requests:
                      storage: 50Gi
  destination:
    server: &lt;a href=&quot;https://kubernetes.default.svc&quot; rel=&quot;nofollow&quot;&gt;https://kubernetes.default.svc&lt;/a&gt;
    namespace: observability&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;One important nuance: ArgoCD renders Helm charts server-side using its own templating engine, not &lt;code&gt;helm install&lt;/code&gt;. This means Helm hooks (&lt;code&gt;pre-install&lt;/code&gt;, &lt;code&gt;post-upgrade&lt;/code&gt;, etc.) &lt;em&gt;are&lt;/em&gt; supported, but the release is not tracked in Helm&amp;rsquo;s release history. Running &lt;code&gt;helm list&lt;/code&gt; will not show ArgoCD-managed releases unless you configure ArgoCD to use the Helm secrets backend.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Projects: Multi-Tenancy and Access Control&lt;/h2&gt;



&lt;p&gt;ArgoCD Projects provide multi-tenancy within a single ArgoCD instance. They let you restrict which source repositories, destination clusters, and namespaces a team can deploy to. Every Application belongs to a Project.&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: platform-team
  namespace: argocd
spec:
  description: Platform team applications
  sourceRepos:
  - '&lt;a href=&quot;https://github.com/your-org/*&quot; rel=&quot;nofollow&quot;&gt;https://github.com/your-org/*&lt;/a&gt;'
  destinations:
  - namespace: 'platform-*'
    server: &lt;a href=&quot;https://kubernetes.default.svc&quot; rel=&quot;nofollow&quot;&gt;https://kubernetes.default.svc&lt;/a&gt;
  clusterResourceWhitelist:
  - group: ''
    kind: Namespace
  namespaceResourceBlacklist:
  - group: ''
    kind: ResourceQuota&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Projects are where you define the boundaries of what each team can do. The &lt;code&gt;default&lt;/code&gt; project has no restrictions &amp;mdash; never use it for production workloads. Create dedicated projects per team or per environment.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;RBAC Configuration&lt;/h2&gt;



&lt;p&gt;ArgoCD has its own RBAC system layered on top of Kubernetes RBAC. It is configured via the &lt;code&gt;argocd-rbac-cm&lt;/code&gt; ConfigMap. Roles are defined per project or globally:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
data:
  policy.default: role:readonly
  policy.csv: |
    # Platform team has full access to platform-team project
    p, role:platform-admin, applications, *, platform-team/*, allow
    p, role:platform-admin, projects, get, platform-team, allow
    p, role:platform-admin, repositories, *, *, allow

    # Dev team can sync but not delete
    p, role:developer, applications, get, */*, allow
    p, role:developer, applications, sync, */*, allow
    p, role:developer, applications, action/*, */*, allow

    # Bind SSO groups to roles
    g, your-org:platform-team, role:platform-admin
    g, your-org:developers, role:developer&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The &lt;code&gt;policy.default: role:readonly&lt;/code&gt; ensures that any authenticated user who has no explicit role assignment gets read-only access &amp;mdash; a safe default for production.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Multi-Cluster Management&lt;/h2&gt;



&lt;p&gt;ArgoCD can manage multiple Kubernetes clusters from a single control plane. Register external clusters with the CLI:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;# First, ensure the target cluster context is in your kubeconfig
argocd cluster add production-eu-west --name production-eu-west

# Verify registration
argocd cluster list&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;ArgoCD will create a ServiceAccount in the target cluster and store its credentials as a Kubernetes secret in the ArgoCD namespace. Applications can then target this cluster by name in their &lt;code&gt;destination.server&lt;/code&gt; field.&lt;/p&gt;



&lt;p&gt;For large-scale multi-cluster setups, consider the &lt;strong&gt;App of Apps&lt;/strong&gt; pattern or &lt;strong&gt;ApplicationSets&lt;/strong&gt;. ApplicationSets are a controller that generates Applications dynamically based on generators &amp;mdash; cluster lists, Git directory structures, or matrix combinations:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: cluster-addons
  namespace: argocd
spec:
  generators:
  - clusters:
      selector:
        matchLabels:
          environment: production
  template:
    metadata:
      name: '{{name}}-addons'
    spec:
      project: platform
      source:
        repoURL: &lt;a href=&quot;https://github.com/your-org/cluster-addons&quot; rel=&quot;nofollow&quot;&gt;https://github.com/your-org/cluster-addons&lt;/a&gt;
        targetRevision: HEAD
        path: 'addons/{{metadata.labels.region}}'
      destination:
        server: '{{server}}'
        namespace: kube-system&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This single ApplicationSet deploys the appropriate addons to every cluster labeled &lt;code&gt;environment: production&lt;/code&gt;, using each cluster&amp;rsquo;s region label to select the correct path in the repository.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Sync Strategies and Waves&lt;/h2&gt;



&lt;p&gt;When deploying complex applications with dependencies between resources, you need to control the order of deployment. ArgoCD provides two mechanisms:&lt;/p&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;Sync Phases&lt;/h3&gt;



&lt;p&gt;Resources are deployed in three phases: PreSync, Sync, and PostSync. Use Sync Hooks for resources that must complete before the main sync proceeds (database migrations, certificate issuance, etc.):&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
  template:
    spec:
      containers:
      - name: migrate
        image: your-app:v1.2.3
        command: [&quot;./migrate.sh&quot;]
      restartPolicy: Never&lt;/code&gt;&lt;/pre&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;Sync Waves&lt;/h3&gt;



&lt;p&gt;Within the Sync phase, waves control ordering. Resources with a lower wave number are applied and must become healthy before resources with higher wave numbers are applied:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;# Applied first
metadata:
  annotations:
    argocd.argoproj.io/sync-wave: &quot;1&quot;

# Applied after wave 1 is healthy
metadata:
  annotations:
    argocd.argoproj.io/sync-wave: &quot;2&quot;&lt;/code&gt;&lt;/pre&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Notifications and Alerting&lt;/h2&gt;



&lt;p&gt;ArgoCD Notifications is a standalone controller that sends alerts when Application state changes. It supports Slack, PagerDuty, GitHub commit status, email, and a dozen other providers. Configure it via the &lt;code&gt;argocd-notifications-cm&lt;/code&gt; ConfigMap:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
  namespace: argocd
data:
  service.slack: |
    token: $slack-token
  template.app-sync-failed: |
    slack:
      attachments: |
        [{
          &quot;title&quot;: &quot;{{.app.metadata.name}}&quot;,
          &quot;color&quot;: &quot;#E96D76&quot;,
          &quot;fields&quot;: [{
            &quot;title&quot;: &quot;Sync Status&quot;,
            &quot;value&quot;: &quot;{{.app.status.sync.status}}&quot;,
            &quot;short&quot;: true
          },{
            &quot;title&quot;: &quot;Message&quot;,
            &quot;value&quot;: &quot;{{range .app.status.conditions}}{{.message}}{{end}}&quot;,
            &quot;short&quot;: false
          }]
        }]
  trigger.on-sync-failed: |
    - when: app.status.sync.status == 'Unknown'
      send: [app-sync-failed]
    - when: app.status.operationState.phase in ['Error', 'Failed']
      send: [app-sync-failed]&lt;/code&gt;&lt;/pre&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Secret Management with ArgoCD&lt;/h2&gt;



&lt;p&gt;ArgoCD intentionally has no secret management built in &amp;mdash; storing secrets in Git as plain text is never acceptable. The common patterns are:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;strong&gt;Sealed Secrets&lt;/strong&gt; (Bitnami) &amp;mdash; Encrypts secrets with a cluster-specific key. The encrypted secret can be committed to Git; only the cluster can decrypt it.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;External Secrets Operator&lt;/strong&gt; &amp;mdash; Syncs secrets from Vault, AWS Secrets Manager, GCP Secret Manager, etc. into Kubernetes secrets. The ArgoCD Application manages the ExternalSecret CRD, not the actual secret value.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;argocd-vault-plugin&lt;/strong&gt; &amp;mdash; A plugin that replaces placeholder values in manifests with secrets retrieved from Vault at sync time.&lt;/li&gt;&lt;/ul&gt;



&lt;p&gt;The External Secrets Operator approach is the most flexible for teams already using a centralized secrets backend. The Application in ArgoCD deploys &lt;code&gt;ExternalSecret&lt;/code&gt; objects, which the ESO controller resolves at runtime without ever touching Git.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Production Best Practices&lt;/h2&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;strong&gt;Run ArgoCD in HA mode.&lt;/strong&gt; Use &lt;code&gt;manifests/ha/install.yaml&lt;/code&gt; with 3 replicas of the API server and multiple application controller shards for large clusters (100+ applications).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Pin image versions.&lt;/strong&gt; Never use &lt;code&gt;latest&lt;/code&gt; for the ArgoCD image itself. Pin to a specific version and upgrade deliberately.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Use the App of Apps pattern for bootstrapping.&lt;/strong&gt; A single root Application deploys all other Applications. This makes cluster bootstrapping idempotent and reproducible.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Separate ArgoCD config from application config.&lt;/strong&gt; Store ArgoCD Application manifests in a dedicated &lt;code&gt;gitops&lt;/code&gt; repository, separate from application source code.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Enable resource tracking via annotations.&lt;/strong&gt; Use &lt;code&gt;application.resourceTrackingMethod: annotation&lt;/code&gt; in &lt;code&gt;argocd-cm&lt;/code&gt; instead of the default label-based tracking, which can conflict with Helm&amp;rsquo;s own labels.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Set resource limits on ArgoCD controllers.&lt;/strong&gt; Application controller CPU and memory scale with the number of resources tracked. Monitor and tune accordingly.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Restrict auto-sync in production.&lt;/strong&gt; Consider requiring manual sync approval for production environments even when using GitOps &amp;mdash; or at minimum require a PR approval gate before changes reach the target branch.&lt;/li&gt;&lt;/ul&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;ArgoCD vs Flux&lt;/h2&gt;



&lt;p&gt;Flux v2 is the other major GitOps operator. Both are CNCF projects. The main differences in practice:&lt;/p&gt;



&lt;figure class=&quot;wp-block-table&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Feature&lt;/th&gt;&lt;th&gt;ArgoCD&lt;/th&gt;&lt;th&gt;Flux v2&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;UI&lt;/td&gt;&lt;td&gt;Built-in web UI&lt;/td&gt;&lt;td&gt;No official UI (use Weave GitOps)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Multi-cluster&lt;/td&gt;&lt;td&gt;Single control plane manages many clusters&lt;/td&gt;&lt;td&gt;Agent per cluster, pull model&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ApplicationSets&lt;/td&gt;&lt;td&gt;Native&lt;/td&gt;&lt;td&gt;Kustomization + HelmRelease&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Secret management&lt;/td&gt;&lt;td&gt;Plugin-based&lt;/td&gt;&lt;td&gt;SOPS native integration&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Learning curve&lt;/td&gt;&lt;td&gt;Steeper (more concepts)&lt;/td&gt;&lt;td&gt;Lower (Kubernetes-native CRDs)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CNCF status&lt;/td&gt;&lt;td&gt;Graduated&lt;/td&gt;&lt;td&gt;Graduated&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;



&lt;p&gt;ArgoCD wins when you need the UI, multi-cluster management from a central plane, or have a large operations team that benefits from the visual application topology view. Flux wins when you want a simpler, purely Kubernetes-native approa&lt;/p&gt;
</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/15641/argocd-guide-gitops-continuous-delivery-for-kubernetes</guid>
<pubDate>Thu, 23 Apr 2026 12:47:43 +0000</pubDate>
</item>
</channel>
</rss>