GitOps Workflow Best Practices: Stopping Config Drift Before It Kills Your Production
I once watched a senior engineer wipe out a critical persistent volume because he thought his terminal context was set to staging-oslo when it was actually prod-eu-west. He ran a manual kubectl apply -f with a typo in the storage class. The downtime cost the company more in three hours than their entire annual hosting bill.
If you are still SSH-ing into servers or manually applying manifests from your laptop, you are a ticking time bomb. In 2025, infrastructure management has zero tolerance for cowboy engineering. We need deterministic, audit-friendly, and automated workflows.
This is where GitOps stops being a buzzword and starts being your insurance policy. It relies on a simple premise: Git is the single source of truth. If itβs not in the repo, it doesnβt exist in the cluster. But implementing this correctly requires more than just installing ArgoCD and hoping for the best. It requires a rigid workflow, especially when dealing with strict data residency requirements here in Norway.
The Pull vs. Push Architecture
Traditional CI/CD pipelines use a "Push" model. Your CI runner (Jenkins, GitLab CI, GitHub Actions) has the keys to your kingdom (your KUBECONFIG). If your CI gets compromised, your entire infrastructure is exposed.
The pragmatic approach for 2025 is the "Pull" model. We install an agent (like ArgoCD or Flux) inside the cluster. This agent monitors the Git repository and pulls changes down. It compares the Desired State (Git) with the Actual State (Cluster) and reconciles them.
Pro Tip: By using a Pull model, you never expose cluster credentials to your external CI provider. This is critical for compliance with strict security standards like ISO 27001, often required by Norwegian enterprise clients.
Structuring Your Repository: Kustomize Overlays
Don't duplicate YAML files for every environment. Use Kustomize to maintain a base configuration and apply overlays for specific environments (dev, stage, prod). This reduces the blast radius of configuration errors.
Here is the folder structure standard we use for high-availability setups:
.
βββ base
β βββ deployment.yaml
β βββ service.yaml
β βββ kustomization.yaml
βββ overlays
βββ dev
β βββ kustomization.yaml
β βββ replica_patch.yaml
βββ prod
βββ kustomization.yaml
βββ resource_limits.yaml
And here is how a production overlay kustomization.yaml should look to enforce high availability:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
# Enforce production-grade replicas
replicas:
- name: backend-api
count: 3
# Patch specifically for production resources
patchesStrategicMerge:
- resource_limits.yaml
# Immutable image tags for audit trails
images:
- name: registry.coolvds.com/my-app
newTag: v1.4.2-release
Managing Secrets: The GDPR Headache
You cannot commit secrets.yaml to Git. If you do, you have breached GDPR before you even started. In Norway, the Datatilsynet (Data Protection Authority) takes a dim view of exposed PII credentials.
In 2025, the standard is External Secrets Operator (integrating with Vault or AWS Secrets Manager) or SOPS (Secrets OPerationS) for encrypted git-native workflows. If you are running on bare-metal or efficient VPS solutions like CoolVDS where you control the whole stack, SOPS with Age encryption is lightweight and incredibly secure.
Encrypting a file locally before commit looks like this:
sops --encrypt --age $(cat key.txt) --in-place secrets.yaml
Your cluster controller then holds the private key to decrypt this on the fly. The repo only sees AES-256 encrypted garbage.
The CI Pipeline's Only Job: Update the Image Tag
Your CI pipeline (e.g., GitLab CI) should not run kubectl. Its only job is to build the Docker image, push it to the registry, and then update the manifest in the Git repo.
Here is a battle-tested GitLab CI job that handles the "Git" part of GitOps safely:
deploy_manifest:
stage: deploy
image: bitnami/git:2.44.0
script:
- git config user.email "ci-bot@coolvds.com"
- git config user.name "CI Bot"
- git checkout -b main
# Update the tag in the kustomization file
- cd overlays/prod
- kustomize edit set image my-app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
# Commit the change
- git add kustomization.yaml
- git commit -m "Bump image to $CI_COMMIT_SHORT_SHA [skip ci]"
- git push origin main
only:
- main
Once this commit lands, ArgoCD detects the change and syncs the cluster.
Infrastructure Performance: The Hidden Bottleneck
GitOps relies heavily on the Kubernetes control plane. The reconciliation loop constantly queries etcd to compare state. If your underlying infrastructure has slow I/O, your etcd latency spikes, and your deployments hang or fail.
Many "budget" VPS providers oversell their storage I/O. They might give you vCPUs, but the disk queue is so long that writing state to etcd takes hundreds of milliseconds. This causes "Context Deadline Exceeded" errors during heavy sync operations.
This is why we architect CoolVDS with pure NVMe storage arrays. When you are running a GitOps controller inside your cluster, you need high IOPS to process the diffs of hundreds of manifests instantly. Low latency isn't just about your website loading fast; it's about your management plane actually functioning.
Furthermore, for Norwegian businesses, keeping this dataβincluding your GitOps state and secretsβwithin Norwegian borders is a massive compliance advantage under Schrems II.
ArgoCD ApplicationSet for Multi-Tenancy
Managing one app is easy. Managing fifty microservices across three clusters is where the pain starts. Use the ApplicationSet controller to automate the generation of ArgoCD applications based on folder structure.
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: cluster-addons
namespace: argocd
spec:
generators:
- git:
repoURL: https://git.coolvds.com/ops/cluster-manifests.git
revision: HEAD
directories:
- path: addons/*
template:
metadata:
name: '{{path.basename}}'
spec:
project: default
source:
repoURL: https://git.coolvds.com/ops/cluster-manifests.git
targetRevision: HEAD
path: '{{path}}'
destination:
server: https://kubernetes.default.svc
namespace: '{{path.basename}}'
syncPolicy:
automated:
prune: true
selfHeal: true
Handling Emergency Hotfixes
"But what if I need to fix something NOW?" This is the most common objection to GitOps. The answer is not to break the workflow, but to optimize it.
If you edit a live resource manually:
kubectl edit deployment/frontend
ArgoCD will immediately mark the application as OutOfSync. If you have selfHeal enabled (as shown above), it will instantly revert your change. This prevents configuration drift. If you need a hotfix, you revert the commit in Git. That is faster and safer than manual editing because it is reproducible.
Latency Matters: The NIX Connection
Your GitOps agent is constantly pulling from your Git repository. If your repo is hosted on GitHub/GitLab, network pathing matters. If you are self-hosting GitLab (common for enterprise privacy), hosting it on a CoolVDS instance in Oslo means your build artifacts and manifest pulls are traversing the NIX (Norwegian Internet Exchange) backbone.
We see pull times drop from 150ms to <10ms compared to hosting in US-East. In a microservices architecture pulling dozens of Docker images, that latency reduction cuts deployment times by minutes.
Final Checklist for 2025
- Everything in Git: Dashboards, alerts, and infra definitions.
- Pull, don't Push: Keep credentials inside the cluster.
- Encrypt Secrets: SOPS or Sealed Secrets are mandatory.
- Infrastructure: Run your control plane on NVMe storage to prevent
etcdtimeout.
GitOps is the standard for a reason. It turns anxiety-inducing deployments into boring, clickable events. But it requires a foundation that keeps up with the churn. Don't let slow I/O kill your reconciliation loop.
Ready to build a rock-solid GitOps platform? Deploy a high-performance NVMe KVM instance on CoolVDS today and experience the stability your code deserves.