Console Login

GitOps in 2025: Stop SSH-ing Into Production or Go Home

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.