GitOps in 2025: Stop SSH-ing Into Production or Go Home
If you are still running kubectl apply -f from your laptop in 2025, you are not a systems administrator; you are a liability. I’ve seen entire clusters in Oslo degrade because one engineer decided to manually "fix" a config map at 2 AM, bypassing the repo. The result? Configuration drift that took three days to diagnose while the customer’s SLA credits burned a hole in the company budget. In the high-stakes environment of Nordic hosting, where latency to the NIX (Norwegian Internet Exchange) is measured in fractions of a millisecond, there is no room for manual cowboy antics.
This is the age of GitOps. Your infrastructure is code. Your state is declarative. And if it’s not in the git commit log, it doesn't exist.
The Iron Law: Declarative State
The core philosophy we enforce is simple: The git repository is the single source of truth. The cluster is just a reflection of that truth. If you manually delete a pod or change a service type, the GitOps operator (ArgoCD or Flux) should smack your hand and revert it immediately.
This isn't just about stability; it's about the strict requirements we face here in Europe. When Datatilsynet (The Norwegian Data Protection Authority) knocks on your door asking for an audit trail of who changed the firewall rules for the payment gateway, you don't show them bash history. You show them the git commit hash.
The Architecture: The Pull Model
Push-based CI/CD (where Jenkins/GitLab pushes to the cluster) is a security nightmare. It requires giving your CI runner admin credentials to your production cluster. If your CI gets breached, your production is toast.
We use the Pull Model. An operator inside the cluster pulls changes. This way, the cluster needs read-only access to the repo. Zero admin keys float around the CI environment.
Pro Tip: When running GitOps controllers like ArgoCD, disk I/O latency matters. The reconciliation loop involves heavy diffing of manifests against cluster state. We run our management clusters on CoolVDS NVMe instances because standard spinning rust (or cheap SATA SSDs) causes the controller queue to back up during large deployments. High IOPS are not optional here.
1. The "App of Apps" Pattern
Managing 50 microservices individually is impossible. We use the App of Apps pattern to bootstrap the cluster. Here is the production-grade manifest we use to deploy the entire stack:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure-root
namespace: argocd
spec:
project: default
source:
repoURL: 'git@github.com:coolvds-ops/cluster-oslo-01.git'
targetRevision: HEAD
path: applications
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
This Application points to a folder containing other Applications. One sync triggers the deployment of Prometheus, Cert-Manager, Ingress-Nginx, and your business apps simultaneously.
Handling Secrets without Leaking Credentials
You cannot check passwords.txt into git. If you do, you have failed. In 2025, the standard is External Secrets Operator (ESO). It integrates with your Vault or Cloud Secret Manager, injecting secrets into the cluster at runtime. However, for smaller deployments or where strict air-gapping is required (common in Norwegian energy sector projects), Sealed Secrets remains a viable, lower-overhead alternative.
Here is how you define an External Secret that fetches a database password from a secure store and creates a Kubernetes Secret:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: mariadb-credentials
namespace: production
spec:
refreshInterval: "1h"
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: db-pass-secret
creationPolicy: Owner
data:
- secretKey: password
remoteRef:
key: secret/data/production/mariadb
property: password
No plaintext. Complete auditability. Compliant.
The CI Pipeline: Building the Artifact
GitOps handles the deployment (CD), but you still need Continuous Integration (CI) to build the container images and update the manifest. Here is a stripped-down GitHub Actions workflow. Note how we do not deploy here; we only update the manifest repo.
name: Build and Bump
on:
push:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and Push Docker Image
run: |
docker build -t registry.coolvds.com/app:${{ github.sha }} .
docker push registry.coolvds.com/app:${{ github.sha }}
- name: Update Manifest Repo
run: |
git clone https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/coolvds-ops/cluster-oslo-01.git
cd cluster-oslo-01
# Use kustomize to update the tag cleanly
cd applications/backend
kustomize edit set image backend=registry.coolvds.com/app:${{ github.sha }}
git config user.name "CoolVDS Bot"
git config user.email "bot@coolvds.com"
git commit -am "bump: update backend to ${{ github.sha }}"
git push
Tooling Showdown: ArgoCD vs. Flux v2
We see both used extensively across European tech hubs. Here is the breakdown based on our infrastructure testing.
| Feature | ArgoCD | Flux v2 |
|---|---|---|
| UI Experience | Excellent dashboard (visualizing topology). | Minimal/None (CLI focused). |
| Multi-Tenancy | Native support (Projects). | Relies on namespace isolation. |
| Resource Usage | High (Redis + Repo Server + App Controller). | Low (Golang binaries, very lean). |
| Best For | Teams needing visibility & UI. | "Set and forget" edge clusters. |
For a managed hosting environment where clients want to see what is happening, ArgoCD is the winner. For raw efficiency on edge nodes, Flux takes the crown.
Infrastructure Fundamentals
You cannot ignore the metal. GitOps relies on the controller constantly checking the repo. If your network latency to the git provider (GitHub/GitLab) is high, or if your DNS resolution is flaky, your sync status will flap.
This is where local geography hits hard. Hosting your control plane on servers physically located in Norway (like CoolVDS) ensures minimal latency to local registry mirrors and compliant data residency. We configure our KVM hosts to prioritize network interrupts, ensuring that webhooks from your git provider are processed instantly.
Quick Diagnostic Commands
When things break (and they will), use these commands to debug the reconciliation loop:
1. Force ArgoCD sync via CLI:
argocd app sync infrastructure-root --prune
2. Check Flux reconciliation status:
flux get kustomizations --watch
3. Decode a Sealed Secret to verify (locally):
kubeseal --recovery-unseal --backup-file=my-secret.json
Conclusion
GitOps is not a feature; it is a discipline. It moves the complexity from the runtime (where mistakes cause outages) to the build time (where mistakes just break the build). By combining strict declarative workflows with robust underlying infrastructure like CoolVDS—which offers the NVMe throughput and network stability required for rapid reconciliation—you build systems that survive the chaos of the modern web.
Don't let slow I/O or manual config drift kill your uptime. Deploy a GitOps-ready KVM instance on CoolVDS today and sleep better tonight.