GitOps — Recap¶
What it is¶
A pattern, not a tool. Git is the single source of truth for everything — infrastructure, application topology, application code. Every change goes through Git. Automation reconciles what's in Git with what's actually running.
No manual SSH to deploy. No "I ran a command on the server." If it's not in Git, it didn't happen.
The three repo structure¶
terraform-ansible-repo → infrastructure layer
VMs, networking, Azure services,
Docker install, Swarm setup, secrets
owner: you, manually, rarely
stack-repo → application topology layer
stack.yml, service definitions
CI does docker stack deploy
owner: you or CI, when services change
code-repo → application source
source code, Dockerfile
CI builds and pushes image
owner: CI, every push
Dependency flows one way:
terraform-ansible → produces the cluster
stack-repo → deploys onto that cluster
code-repo → produces the images stack-repo references
Nothing flows backwards.
The full end to end flow¶
1. terraform apply
→ VMs provisioned, VNet, NSG, Blob Storage, Key Vault, Function
→ Service Principal created, role assignments done
2. ansible-playbook playbook.yml
→ Docker installed on manager and worker
→ Swarm initialised on manager
→ Worker joined to Swarm
→ Azure SP credentials loaded as Docker secrets
3. git push to code-repo
→ GitHub Actions builds image
→ Tags with :latest and :<git-sha>
→ Updates image tag in stack-repo stack.yml
→ Pushes to stack-repo master
4. stack-repo CI triggers on push
→ SSHes into manager
→ docker stack deploy
→ app is running
Steps 1 and 2 are one-time. Steps 3 and 4 happen on every code change.
Cadence of each repo¶
terraform-ansible-repo → provision once, touch rarely
new VM, new Azure service, NSG rule change
stack-repo → touch when services change
new service, new env var, new volume, image tag update
code-repo → every feature, every fix
fully automated, no manual steps
How the image tag flows between repos¶
In code-repo GitHub Actions:
SHA=$(git rev-parse --short HEAD)
# build and push
docker build -t ghcr.io/abhishek052off/averazure:$SHA .
docker push ghcr.io/abhishek052off/averazure:$SHA
docker push ghcr.io/abhishek052off/averazure:latest
# update stack-repo
sed -i 's|ghcr.io/abhishek052off/averazure:.*|ghcr.io/abhishek052off/averazure:'$SHA'|' stack.yml
git commit -am "update image tag to $SHA"
git push stack-repo master
stack-repo always reflects exactly what is deployed. Rollback is reverting a commit.
GitOps in Kubernetes world¶
In K8s, tools like ArgoCD and Flux do what your GitHub Actions does — but continuously. They poll the repo and sync the cluster state to match. If someone manually changes something on the cluster, ArgoCD reverts it back to what Git says.
Your GitHub Actions only syncs on push. ArgoCD syncs constantly. Same pattern, different implementation.
Full runnable code — GitHub Actions pipelines¶
code-repo pipeline¶
.github/workflows/build.yml
name: Build and Deploy
on:
push:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GHCR_TOKEN }}
- name: Build and push image
run: |
SHA=$(git rev-parse --short HEAD)
docker build -t ghcr.io/abhishek052off/averazure:$SHA .
docker build -t ghcr.io/abhishek052off/averazure:latest .
docker push ghcr.io/abhishek052off/averazure:$SHA
docker push ghcr.io/abhishek052off/averazure:latest
echo "SHA=$SHA" >> $GITHUB_ENV
- name: Checkout stack-repo
uses: actions/checkout@v3
with:
repository: abhishek052off/stack-repo
token: ${{ secrets.GHCR_TOKEN }}
path: stack-repo
- name: Update image tag in stack.yml
run: |
sed -i 's|ghcr.io/abhishek052off/averazure:.*|ghcr.io/abhishek052off/averazure:${{ env.SHA }}|' stack-repo/stack.yml
cd stack-repo
git config user.email "ci@averazure.com"
git config user.name "CI Bot"
git commit -am "update image tag to ${{ env.SHA }}"
git push
GitHub Secrets needed in code-repo:
GHCR_TOKEN— PAT with read/write packages and repo access
stack-repo pipeline¶
.github/workflows/deploy.yml
name: Deploy Stack
on:
push:
branches: [master]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GHCR_TOKEN }}
- name: Deploy to Swarm
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.MANAGER_IP }}
username: azureuser
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
echo ${{ secrets.GHCR_TOKEN }} | docker login ghcr.io -u abhishek052off --password-stdin
docker stack deploy -c /home/azureuser/stack.yml aver --with-registry-auth
- name: Copy updated stack.yml to manager
uses: appleboy/scp-action@v0.1.4
with:
host: ${{ secrets.MANAGER_IP }}
username: azureuser
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: stack.yml
target: /home/azureuser/
GitHub Secrets needed in stack-repo:
GHCR_TOKEN— PAT with read packagesMANAGER_IP— public IP of aver-managerSSH_PRIVATE_KEY— your private key contents
stack.yml (lives in stack-repo)¶
version: "3.8"
services:
aver_api:
image: ghcr.io/abhishek052off/averazure:latest
environment:
- ASPNETCORE_URLS=http://0.0.0.0:8080
secrets:
- azure_tenant_id
- azure_client_id
- azure_client_secret
ports:
- "8080:8080"
networks:
- aver_net
deploy:
replicas: 1
update_config:
order: start-first
aver_rabbitmq:
image: rabbitmq:3-management
volumes:
- rabbitmq_data:/var/lib/rabbitmq
ports:
- "15672:15672"
networks:
- aver_net
deploy:
replicas: 1
placement:
constraints:
- node.hostname == aver-manager
aver_seq:
image: datalust/seq:latest
environment:
- ACCEPT_EULA=Y
volumes:
- seq_data:/data
ports:
- "8081:80"
networks:
- aver_net
deploy:
replicas: 1
placement:
constraints:
- node.hostname == aver-manager
secrets:
azure_tenant_id:
external: true
azure_client_id:
external: true
azure_client_secret:
external: true
volumes:
rabbitmq_data:
seq_data:
networks:
aver_net:
driver: overlay
The one liner for interviews¶
"Infrastructure, application topology, and application code live in separate repos with separate cadences. Git is the source of truth. A push to code-repo triggers image build and tag update in the stack repo, which triggers deploy. Nothing is done manually after the initial cluster setup."