Most GitOps tutorials assume a managed cloud Kubernetes cluster with built-in load balancers, IAM-based container registry access, and ready-made secret management. In a real self-hosted GitOps pipeline setup, you get none of that for free.
This guide covers everything, including deploying a bare-metal Kubernetes cluster with ArgoCD, MetalLB, NGINX Ingress, app-of-apps GitOps, encrypted secrets, and secure WireGuard connectivity. All tuned for a production-ready self-hosted GitOps pipeline setup.
Why Use GitOps on Bare Metal?
A self-hosted GitOps pipeline setup gives you full control over your servers, your budget, and where your data lives. Instead of relying on a cloud provider’s control plane, you run Kubernetes clusters on dedicated machines you own or rent from a provider you trust.
On bare metal, you store all infrastructure, add-ons, and application configs in Git, and ArgoCD keeps your cluster in sync with that repository. This continuous sync is the core idea behind any self-hosted GitOps pipeline setup.
If you need reliable hardware for Kubernetes and GitOps, you can run this self-hosted GitOps pipeline setup on high-performance dedicated servers built for self-managed DevOps workflows.
Prerequisites for Self-Hosted GitOps Pipeline Setup
Before you start, make sure to have the prerequisites ready for a self-hosted GitOps pipeline setup:
One or more dedicated servers running Ubuntu 22.04 or newer.
Kubernetes cluster, kubeadm or k3s, up and running, with kubectl configured.
Static IPs and a routable LAN segment for MetalLB.
Git repository, GitHub, GitLab, Gitea, etc., for your GitOps manifests.
Note: For GPU workloads inside the same self-hosted GitOps pipeline setup, you can consider dedicated GPU servers optimized for Kubernetes and AI/ML hosting.
Step 1. Create a Bare-Metal Kubernetes Cluster
If your cluster isn’t ready yet, you can follow the steps below to set up Kubernetes on bare-metal for a reliable self-hosted GitOps pipeline setup.
First, make sure the node has the right kernel networking settings and swap disabled so kubeadm’s preflight checks pass:
Bash
# Disable swap (required for kubelet)sudoswapoff-a# Disable swap permanently (comment out the swap line)sudosed-i'/ swap / s/^/#/'/etc/fstab
Then enable bridge netfilter and IP forwarding, which Kubernetes needs to route pod traffic:
You must install a CNI plugin such as Flannel by applying the kube-flannel.yml manifest, which sets up pod networking across all nodes in your cluster:
Step 3. Install NGINX Ingress Controller with LoadBalancer Service
NGINX Ingress is the component that routes external HTTP/HTTPS traffic into services running inside your cluster. In a self-hosted GitOps pipeline setup on bare metal, you don’t get a cloud ingress or load balancer by default, so you can install NGINX Ingress yourself and expose it via a LoadBalancer service backed by MetalLB.
You should see an EXTERNAL-IP assigned by MetalLB, which becomes the main entrypoint for HTTP/HTTPS traffic.
Step 4. Install ArgoCD on Kubernetes
ArgoCD is the GitOps engine that keeps your cluster in sync with what’s defined in Git. In this step, you’ll install the latest stable ArgoCD release on your Kubernetes cluster so your self-hosted GitOps pipeline setup can continuously apply and reconcile manifests from your Git repository.
Once ArgoCD is running, every change you push to Git can be safely rolled out to your bare-metal cluster with full visibility and automated rollback.
Create namespace and apply stable install manifest:
This gives you a current ArgoCD release suitable for a robust self-hosted GitOps pipeline setup. For HA, you can use ha/install.yaml in the same repository.
By default, argocd-server is ClusterIP. For initial configuration, use port-forwarding:
The app-of-apps pattern is a way to structure your Git repository so one ArgoCD Application can manage many other Applications. This makes your self-hosted GitOps pipeline setup easier to set up, easier to grow, and much simpler to keep consistent across environments.
This structure makes Git the single place where you define everything in your self-hosted GitOps pipeline setup, such as your core infrastructure, extra add-ons, and all application workloads.
Root app-of-apps Application:
This is the entry point for your GitOps setup. You define it in clusters/prod/root-app.yaml inside your Git repo, and it tells ArgoCD to manage everything under clusters/prod/ as a group of child applications. When you sync this root app, ArgoCD automatically creates and updates all the other apps and infrastructure pieces in your self-hosted GitOps pipeline setup.
In a self-hosted GitOps pipeline setup, image pulls often hit a self-hosted registry without IAM integration. You must declare registry credentials explicitly.
ArgoCD syncs this, and your pods can pull private images.
Secrets Management with SOPS
Because your self-hosted GitOps pipeline setup doesn’t necessarily include a managed secret store, SOPS lets you safely keep secrets in Git.
Install SOPS locally and pick a key backend, age, or GPG.
Write Kubernetes Secret manifests with plaintext values.
Run sops -e secret.yaml > secret.enc.yaml and commit the encrypted file.
Deploy a SOPS-aware controller, such as ksops, sops-operator, or a Helm plugin, in your cluster.
When ArgoCD syncs, it applies the encrypted secret manifests, the SOPS controller decrypts them, and the workloads get their secrets, all while your self-hosted GitOps pipeline setup stays fully defined in Git and easy to review.
Storage Classes and Persistent Volumes for Bare Metal
On bare-metal Kubernetes, you don’t get cloud storage classes or managed disks. To run databases and other stateful apps in a self-hosted GitOps pipeline setup, you must define your own StorageClasses and PersistentVolumes so the cluster knows where and how to create data volumes.
Save this YAML as infra/storageclass/local-path-sc.yaml in your Git repo, and let ArgoCD deploy it through an infra-storage-app Application. Then any PVC that uses storageClassName: local-path will get local disk, so your stateful apps are fully supported in your self-hosted GitOps pipeline setup.
For clusters with multiple nodes, use shared storage like NFS or Ceph/Rook, and manage its configuration in Git as part of your self-hosted GitOps pipeline setup.
Secure Git Connectivity
If your cluster is on a private network, your self-hosted GitOps pipeline setup still needs a secure way to reach the Git server. Headscale, a self-hosted Tailscale control server, lets you do that using access rules and an encrypted WireGuard network.
Typical approach:
Deploy Headscale and join Kubernetes nodes and the Git server/VM as clients.
Use Headscale ACLs to restrict which IPs can reach Git.
Write Kubernetes NetworkPolicy objects to allow egress from argocd namespace to Headscale IP ranges and block other outbound access.
If you want a full setup of deploying Headscale as your self-hosted mesh VPN control plane, check this guide on self-hosting Headscale on Ubuntu, including TLS, reverse proxy, and client setup.
Connect ArgoCD to the GitOps Repository
Now you must wire ArgoCD to the Git repository that defines your self-hosted GitOps pipeline setup.
Use ArgoCD’s automatic sync and self-heal features with health checks so your self-hosted GitOps pipeline setup can recover quickly from bad deployments.
ArgoCD keeps checking whether your resources are healthy, and if a new change breaks things, it automatically goes back to the last working version. That way, your self-hosted GitOps pipeline setup can stay reliable.
Conclusion
Building a modern self-hosted GitOps pipeline setup on bare-metal or dedicated servers means solving things that cloud Kubernetes usually does for you. Such as load balancing without a cloud LB, storage classes without cloud disks, private registry access without IAM, and secure Git access without a managed service mesh.
By using ArgoCD, MetalLB, NGINX Ingress, SOPS, and Headscale/WireGuard together, you get a Git-driven, health-checked, fully self-managed GitOps pipeline setup that’s ready for production.
We hope you enjoy this guide.
Use SOPS to encrypt your secret YAML files, and run a controller in the cluster that can decrypt them when ArgoCD applies the manifests.
Yes. You can add multiple cluster contexts and manage them via projects and Applications.
Yes. All components work on k3s as well, with the same GitOps patterns.
We use cookies to run PerLod and to understand how the site is used. You can accept everything, or open Manage to turn individual categories off. Cookie policy.