Example: Local GitOps Round Trip
Stand up an entire GitOps loop on your laptop — no GitHub account, no real cluster, no cloud credentials — and watch a change travel all the way around:
atmos render ─push─▶ Gitea (Git server emulator) ─watch─▶ Flux ─apply─▶ k3s (Kubernetes emulator)
▲ │
└────────────────────────── you observe it running ◀───────────────────────┘
Two local emulators do the heavy lifting:
gitserver— a Gitea Git server (thegiteaemulator driver). Atmos auto-bootstraps a throwaway admin (atmos/atmos) and adeploymentsrepository. Gitea serves Git over HTTP, which is what Flux needs to watch a repository (a baregit daemonspeakinggit://cannot be watched).kubernetes— a k3s cluster (thek3semulator driver). Flux runs inside it and reconciles thedeploymentsrepository.
Both emulators join a shared per-stack network, so Flux (in k3s) reaches Gitea by
name at http://gitserver:3000, while Atmos pushes from the host to the same
server on http://localhost:3000.
Try It
Prerequisites: a container runtime (Docker or Podman) and Terraform/OpenTofu are not needed here — only the container runtime. Then:
cd examples/local-gitops
# The whole loop in one command:
atmos gitops up
atmos gitops up runs these steps (run them by hand to watch each stage):
# 1. Start the Git server emulator (Gitea bootstraps admin + `deployments` repo)
atmos emulator up gitserver -s local
# 2. Start the Kubernetes emulator (k3s)
atmos emulator up kubernetes -s local
# 3. Install Flux into the cluster, then point it at the local Gitea repository.
# The flux component pulls its install manifest just-in-time from the pinned
# Flux release (via its `source:` field) the first time it is applied.
atmos kubernetes apply flux -s local --identity local-k3s
atmos kubernetes apply flux-sync -s local --identity local-k3s
# 4. Render the demo app's manifests and PUSH them to Gitea (no cluster contact)
atmos kubernetes apply demo-app -s local --target deployments
Now watch the round trip complete — Flux pulls the commit Atmos just pushed and applies it:
# Browse the pushed commit in Gitea's UI (login atmos / atmos)
open http://localhost:3000/atmos/deployments
# Confirm Flux reconciled it INTO the cluster (it appears within ~30s once Flux is
# ready). `emulator exec` runs a command inside the k3s container, which bundles kubectl:
atmos emulator exec kubernetes -s local -- kubectl get ns gitops-demo
atmos emulator exec kubernetes -s local -- kubectl -n gitops-demo get configmap demo-app -o yaml
atmos emulator exec kubernetes -s local -- kubectl -n flux-system get gitrepository,kustomization
Make a change and push again to see it flow around:
# Edit vars.message in stacks/catalog/demo-app.yaml, then:
atmos kubernetes apply demo-app -s local --target deployments
# Flux reconciles the new commit; the ConfigMap updates in-cluster.
Tear it all down:
atmos gitops down # stop + remove both emulators AND the managed clone
# atmos emulator reset kubernetes -s local # also wipe cluster state
Re-running
atmos gitops upafter adownstarts from a freshgitserver(it is ephemeral) and a fresh managed clone, so they always line up. Within one session thegitserverstays up, so editing and re-applyingdemo-appsimply fast-forwards the repository.
How the Push and the Pull Differ
| URL | Why | |
|---|---|---|
| Atmos push (host) | http://atmos:atmos@localhost:3000/... | Atmos runs on the host and pushes through Gitea's published port |
| Flux pull (in-cluster) | http://gitserver:3000/... | Flux runs in k3s and reaches Gitea over the shared emulator network by alias |
Both point at the same Gitea server — one from outside, one from inside.
Key Files
| File | Purpose |
|---|---|
atmos.yaml | git.repositories.deployments (local Gitea), the local-k3s identity, and the atmos gitops up / atmos gitops down commands |
stacks/catalog/emulator/gitea.yaml | The Git server emulator (driver: gitea) |
stacks/catalog/emulator/kubernetes.yaml | The Kubernetes emulator (driver: k3s) |
stacks/catalog/flux.yaml | flux (controllers, pulled JIT via its source: field) + flux-sync (GitRepository/Kustomization/Secret pointing at Gitea) |
stacks/catalog/demo-app.yaml | The app delivered through the loop, with a git provision target |
The Bigger Picture
This is the Kubernetes-native vision in miniature: a component is rendered, committed, and pushed to Git, and the cluster self-reconciles from there. Swap the Gitea emulator for a real Git host and the k3s emulator for a real cluster, and the exact same Atmos components drive production GitOps.