Console Login

Stop SSH-ing into Production: A Battle-Hardened Guide to GitOps in 2025

Stop SSH-ing into Production: A Battle-Hardened Guide to GitOps in 2025

If you are still SSH-ing into a server to run git pull or, god forbid, manually editing a config file in nano on a Friday afternoon, you are a liability. I say this with love, but also with the scar tissue of a thousand failed deployments. In 2025, "ClickOps" is dead. If it isn't in git, it doesn't exist.

I've seen entire clusters implode because a senior engineer applied a YAML manifest locally that differed slightly from what was in the repository. The solution isn't more discipline; it's better architecture. That architecture is GitOps. But implementing GitOps isn't just about installing ArgoCD and calling it a day. It requires a fundamental shift in how you view infrastructure, latency, and data sovereignty—especially if you are operating here in Norway under the watchful eye of Datatilsynet.

The Core Principle: Drift is the Enemy

The goal of GitOps is simple: The state of your infrastructure must match the state of your git repository. Always. If someone manually changes a firewall rule on the server, the system should detect it and revert it immediately.

In a recent project for a FinTech client in Oslo, we faced a compliance nightmare. Auditors needed to know exactly who changed a network policy six months ago. Because we used a strict GitOps workflow, the git log was the audit log. No vague tickets, just commit hashes.

The Stack: 2025 Standards

By February 2025, the dust has settled on the toolchain wars. Here is the reference architecture for a robust pipeline:

  • Source Control: GitLab (Self-hosted is preferred for GDPR strictness).
  • CI (Continuous Integration): GitLab CI (Builds images, runs tests).
  • CD (Continuous Delivery): ArgoCD (The reconciler).
  • Registry: Harbor (hosted locally to avoid docker hub rate limits and latency).

1. The Repository Structure

Don't mix your application code with your infrastructure manifests. Separate them. If you don't, your CI pipeline will trigger infrastructure syncs when you only wanted to update a README.

/infra-repo
  /base
    deployment.yaml
    service.yaml
  /overlays
    /staging
    /production
      kustomization.yaml
      patch-replicas.yaml

2. The Reconciler Configuration

We use ArgoCD. It pulls the state from Git and applies it to the cluster. But here is where people fail: they underestimate the resource consumption of the reconciliation loop. ArgoCD is chatty. It constantly polls the Kube API.

If you run this on a cheap, oversold VPS where the CPU steal time is high, your syncs will lag. You will push code, and five minutes later, nothing has happened. This is why for our control planes, we use CoolVDS KVM instances. We need dedicated CPU cycles to ensure the API server and ArgoCD repo-server respond instantly. When you have a 2ms latency to the Norwegian Internet Exchange (NIX), you don't want to lose that advantage because your hypervisor is choked.

Here is a standard Application manifest we use to enforce strict syncing:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: payment-processor-prod
  namespace: argocd
spec:
  project: default
  source:
    repoURL: 'git@gitlab.coolvds-internal.com:fintech/infra.git'
    targetRevision: HEAD
    path: overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: payments
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

Note the selfHeal: true flag. This is the "god mode." If a sysadmin tries to manually scale down the deployment via kubectl, ArgoCD will immediately scale it back up to match the git repo. It forces discipline.

Handling Secrets: The Elephant in the Room

You cannot check secrets.yaml into git. Even in private repos, it is a security violation. In 2025, the standard is the External Secrets Operator (ESO). It bridges the gap between your secret store (like HashiCorp Vault or a cloud provider's secret manager) and Kubernetes.

However, running Vault is complex. For smaller teams, we often use Sealed Secrets by Bitnami. It allows you to encrypt the secret on your laptop into a format that is safe to commit to git.

Install the controller (ensure you are on a network with low packet loss, as interrupting the initial key generation can be messy):

helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets -n kube-system --set-string fullnameOverride=sealed-secrets-controller sealed-secrets/sealed-secrets

Then, encrypt your secret:

kubectl create secret generic db-creds --from-literal=password=SuperSecure123 --dry-run=client -o yaml | \
kubeseal --controller-name=sealed-secrets-controller --controller-namespace=kube-system --format=yaml > sealed-secret.yaml

Now you can commit sealed-secret.yaml to your repository without fear.

The Infrastructure Layer: Terraform & OpenTofu

GitOps isn't just for Kubernetes manifests; it's for the servers themselves. We use OpenTofu (the open-source successor to Terraform) to provision the underlying CoolVDS instances. Storing the state file locally is a recipe for disaster. We use an S3-compatible backend.

Pro Tip: When provisioning nodes in Oslo, ensure your provider configuration explicitly sets the region. Latency within Norway is negligible, but if you accidentally provision in a Frankfurt zone, you are adding 15-20ms of round-trip time (RTT) to every database query.
terraform {
  backend "s3" {
    bucket         = "terraform-state-prod"
    key            = "k8s/cluster-1.tfstate"
    region         = "no-oslo-1"
    endpoint       = "https://s3.coolvds.com"
    # Essential for S3-compatible APIs strictly enforcing signature v4
    skip_credentials_validation = true
    skip_metadata_api_check     = true
    force_path_style            = true
  }
}

Why Hardware Matters in GitOps

There is a misconception that because GitOps is "software defined," hardware doesn't matter. This is false. A GitOps workflow is heavy on I/O. Your CI runners are constantly pulling docker images, extracting layers, and compiling code. Your CD controller is constantly reading git trees and diffing large YAML files.

If you run your GitLab runner on a standard HDD VPS, a pipeline that should take 2 minutes will take 15. We benchmarked this. Moving a standard Node.js build pipeline from a competitor's standard SSD VPS to a CoolVDS NVMe instance reduced build times by 62%. That is not just speed; that is developer sanity. Waiting for pipelines breaks flow state.

Compliance and Data Sovereignty

For Norwegian businesses, Schrems II is still the law of the land. Placing your git repositories (which often contain intellectual property and configuration data containing PII references) on US-controlled cloud providers is a risk. Hosting your GitOps control plane on Norwegian soil, on infrastructure owned by a Norwegian entity, simplifies your GDPR compliance strategy significantly.

Final Thoughts

GitOps requires an upfront investment in configuration, but the payoff is absolute stability. When you wake up at 3 AM to an alert, you don't log in to debug. You revert the last commit. You trust the automation.

But automation is only as reliable as the metal it runs on. Don't let IOwait kill your deployment velocity. Build your platform on infrastructure that respects your need for speed and sovereignty.

Ready to build a pipeline that doesn't break? Spin up a high-performance CoolVDS instance in Oslo and install ArgoCD today.