On this page
How to Set Up GitOps on a Single-Node k3s Cluster
Point Argo CD at a Git repository and let it drive a single-node k3s cluster: automatic sync, drift correction, and rollbacks that are git reverts instead of kubectl commands.
Quick answer
- GitOps means the repository is the source of truth and a controller in the cluster reconciles reality against it — nobody runs kubectl apply by hand.
- Argo CD and Flux are the two common controllers; both run fine on a single-node k3s cluster if you give them resource limits.
- Automatic sync with self-healing turns a manual cluster edit into a change that reverts itself, which is the point.
- Secrets do not belong in the repository as plaintext; use a sealed-secrets or external-secrets mechanism instead.
- On one node, the GitOps controller is a single point of failure too — if the node is gone, nothing is reconciling until it returns.
Before you start
You need a working k3s cluster, kubectl access, and a Git repository to hold manifests. Argo CD itself needs somewhere to live: on a single-node cluster, plan for a few hundred megabytes of memory and set resource limits on it so it cannot starve your application pods. Decide what the repository will contain before you install anything, because retrofitting a layout after the controller is watching is more work than choosing one now. A simple structure of one directory per environment, each with the manifests for that environment, is enough to start.
# Repo layout
# deploy/
# base/ shared manifests
# staging/ kustomization or overlays for staging
# production/ what the cluster actually runsStep 1 — Get every manifest into Git
The controller can only reconcile what the repository describes. Export the objects you are currently running with kubectl get -o yaml, strip the runtime status fields, and commit them. Anything you leave out — a Secret you created imperatively, a PVC you made by hand — will either be deleted by pruning or silently drift out of the repository’s picture of the cluster. Do not commit plaintext credentials; wire up a secrets mechanism or create the Secret separately and exclude it deliberately.
Step 2 — Install Argo CD
Install into its own namespace, wait for the pods, and reach the UI through a port-forward rather than exposing it publicly on day one. The initial admin password is generated into a Secret you can read with kubectl.
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl -n argocd rollout status deploy/argocd-server
# Initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d
# Reach the UI locally
kubectl -n argocd port-forward svc/argocd-server 8080:443Step 3 — Declare the application
An Application resource tells the controller which repository path maps to which cluster and namespace. With automated sync, pruning, and self-healing enabled, the cluster is continuously corrected toward the repository — a manual kubectl edit gets reverted, which is exactly what you want once you trust the pipeline. Create this resource as a file in Git as well, so the controller’s own configuration is versioned too.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/acme/app
targetRevision: main
path: deploy/production
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: trueStep 4 — Verify drift correction and test a rollback
Confirm the loop actually closes. Change a replica count by hand with kubectl scale and watch the controller report OutOfSync and revert it. Then make a real change through Git — edit the manifest, commit, and watch the new state appear — and test the rollback path by reverting the commit, which is the whole promise of GitOps: recovery is a commit, not a message to whoever has the kubeconfig. If self-healing surprises you by deleting something, remember that pruning removes objects the repository no longer declares.
Step 5 — Keep the single node in mind
Two habits matter on one node. First, cap the controller: Argo CD’s own components should have resource requests and limits so a reconciliation loop cannot crowd out your app. Second, stop mixing methods — once the controller owns a namespace, every manual kubectl change there is a future revert, and the confusion that causes is the main reason people conclude GitOps is flaky. Exclude the namespaces you do not want managed, and put k3s’s own system namespace out of scope. And remember the controller cannot reconcile a node that is down, so keep your external monitoring in place.
Where this bites vibecoders
An assistant will install Argo CD and hand you a working UI in one response, and the setup will look finished long before it is trustworthy. Three things it tends to skip: resource limits on the controller, so it competes with your app for a 2 GB node; a secrets strategy, so plaintext credentials end up in the repository the AI itself has write access to; and an explicit decision about which namespaces are managed, so pruning deletes objects nobody intended to remove. GitOps on a single node is genuinely a good fit — it is the discipline around it that has to be deliberate rather than generated.
Where AI coding assistants get this wrong
- Committing plaintext Secrets to the repository the controller syncs from.
- Enabling automatic pruning without knowing which objects the repository no longer declares.
- Installing the controller with no resource limits on a small single node.
- Leaving half the cluster managed by GitOps and half by manual kubectl commands.
- Exposing the Argo CD UI on a public LoadBalancer with the default admin account.
- Assuming GitOps provides redundancy, when the controller runs on the same single node as everything else.
Checklist
- Keep every manifest in Git, including the Application resource itself.
- Use a sealed-secrets or external-secrets mechanism instead of plaintext Secrets.
- Set resource requests and limits on the GitOps controller components.
- Enable automated sync with prune and self-heal deliberately, not by default.
- Decide and document which namespaces are managed.
- Test drift correction by editing the cluster by hand and watching it revert.
- Test a rollback as a git revert before you need one.
- Keep external monitoring, because the controller cannot fix a down node.
FAQ
Argo CD or Flux for a small k3s cluster?
Both work and both have small footprints. Argo CD is easier to start with because it has a UI that shows sync status per application, which helps while you are learning what the controller does. Flux is more lightweight and CLI-first. Pick either and stay with it; the operational difference for one cluster is small.
How do I handle secrets in a GitOps repository?
Never commit them in plaintext. The two common approaches are sealing them so only the cluster can decrypt them, or storing them in an external secret manager and syncing references into Kubernetes Secrets. Both keep the repository safe to read while still letting the controller manage the objects.
Can I still use kubectl if GitOps is managing the cluster?
You can, but self-healing will revert changes to managed resources. Use kubectl freely for reading and debugging, and make changes through Git for anything the controller owns. For deliberate manual work, disable auto-sync on that application first, or work in a namespace outside the managed scope.
Related topics
- What Is GitOps?
- How to Set Up GitOps With Argo CD
- k3s for Vibecoders: Kubernetes on One Cheap Server, Without the Cloud Bill
- How to Deploy an App to a Cheap VPS With k3s
- How to Upgrade a Kubernetes Cluster Without Downtime
- What Is k3s (and When Should You Use It)?