GitOps — Ship Microservices with GitHub Actions and ArgoCD
The problem with traditional CI/CD:-
GitOps — Ship Microservices with GitHub Actions and ArgoCD

The problem with traditional CI/CD:-
If you’ve ever shipped microservices to Kubernetes, you’ve probably lived through this workflow (the old way):
-
Developer pushes code to
main. -
CI pipeline builds a Docker image and pushes it to a registry.
-
CI pipeline then
kubectl applys the new manifests directly to the cluster. -
Something breaks. You roll back by re-running a previous pipeline job, hunting for the old image tag in your CI logs.
There are three challenges hiding in that innocent-looking list:
- The cluster is mutated from outside:- Your CI tool holds
cluster-admincredentials, which is a security bad practice.
- State lives in the CI tool, not in Git:- The “source of truth” for what’s running in production is a pipeline run, not a commit. Ask “what’s in prod right now?” and you have to query the cluster, not the repo.
- Rollback is an operation, not a state:- You can’t just
git revert, you have to orchestrate a redeploy.
GitOps flips all the above problems:-
- Git is the single source of truth, and an agent inside the cluster continuously reconciles the cluster state to match what’s in Git.
- Nothing mutates the cluster from outside. ArgoCD (the agent) runs inside the cluster and pulls from Git.
- “What’s in prod?” == “What’s in
main?”. Just look at the repo.
- Rollback ==
git revert. ArgoCD picks up the change and reconciles.
The pipeline I am going to create, splits the work in two halves:-

Before provisioning the infra and starting the activity, let’s understand what all components this code with deploy on AWS:-
- It will deploy, VPC, networking resources, ECR, EKS (this module also has AWS Load balancer controller, ArgoCD deployed via helm)
- Github OIDC role, so that github action can push the image to ECR repos.
# Let's provision the infra on AWS
# Let me show you how, using a real project: linkshrink, a URL shortener
# build as five Node/TypeScript microservices, deployed to AWS EKS.
git clone sha2121/gh-actions-argocd
cd gh-actions-argocd/tf-infra
terraform init
terraform apply
# Update kubeconfig
aws eks update-kubeconfig --region us-east-1 --name dev-eks-cluster
# Apply the argocd resource
# ArgoCD Application will syncs the web-app Helm chart from this repo
kubectl apply -f argocd/01-applications.yaml

Entire flow at a glance
- Now, whenever any new changes are pushed to ‘main’ branch, the Github actions will re-build the image and put a new tag on ‘chart/values.yaml’, which ArgoCD will detect and deploy on EKS.
- Before triggering the workflow, let’s understand the workflow yaml as well:-
# cat .github/workflows/build-push-app.yaml
name: Build and Push Web App to ECR
on:
push:
branches: [ "main" ]
paths:
- "app/**"
#- ".github/workflows/build-push-app.yaml" # During testing phases only for prod comment this line
permissions:
id-token: write # Needed for OIDC
contents: write # Needed to push commits
env:
AWS_REGION: us-east-1
ECR_REPOSITORY: web-app
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
concurrency:
group: gitops-push
cancel-in-progress: false
jobs:
build-and-push-app:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::311902596413:role/github-actions-oidc-role
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Define image tags
id: vars
run: |
SHA_TAG=sha-${GITHUB_SHA::7}
IMAGE_BASE="${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}"
echo "IMAGE_BASE=$IMAGE_BASE" >> $GITHUB_ENV
echo "TAGS=latest $SHA_TAG" >> $GITHUB_ENV
echo "Using tags: latest and $SHA_TAG"
echo "latest = convenience tag for testing"
echo "$SHA_TAG = immutable tag used in Helm values"
- name: Build and push Docker images
run: |
for tag in $TAGS; do
IMAGE_URI="$IMAGE_BASE:$tag"
echo "Building and pushing $IMAGE_URI"
docker build -t $IMAGE_URI app
docker push $IMAGE_URI
done
- name: Setup Git auth using GITHUB_TOKEN
run: |
git config --global user.name "ci-bot"
git config --global user.email "ci-bot@abc.com"
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
- name: Update Helm values file with new image tag
run: |
cd chart
git config --global user.name "abc-ci"
git config --global user.email "ci@abc"
sed -i "s|^ tag: .*| tag: sha-${GITHUB_SHA::7}|" values-app.yaml
git diff --quiet || (
git add values-app.yaml
git commit -m "Update app image tag to sha-${GITHUB_SHA::7}"
git push origin main
)
- name: CI Complete
run: |
echo "Image push and Git update done"
echo "Tags used: $TAGS"
- Triggers on push to main when files under app/** change, using OIDC (id-token: write) so GitHub Actions can assume an AWS IAM role.
- Builds the app’s Docker image from app/ and pushes two tags to ECR: latest (convenience) and sha-<7chars> (immutable, used by Helm)
- Commits the new image tag back to chart/values-app.yaml in the same repo, ArgoCD detects this Git change and auto-syncs the rolling update to EKS.
- Once, entire infra is deployed, make some random manual changes in the index.html file in app directory and push the changes:-
# app/index.html line 39
Ship faster with <span class="grad">GitOps-driven</span> cloud delivery V1 # change to V1
# Commit and push the changes
git add app/index.html
git commit -m "trigger CI"
git push
- In the actions section of the repository, we can see the workflow being triggered:-

- Once the workflow has finished execution, the image will be pushed to ECR as follows:-

- Go to chart/values-app.yaml, the tag is automatically updated by the ci with the new tag:-

- Wait for few minutes until ArgoCD picks up the changes and deploy this app in EKS:-
# List the pods in default namespace
kubectl get pods
# To check the deployment on Argo UI:-
# Get the admin password
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d ; echo
# Port-forward the server to localhost:8080
kubectl -n argocd port-forward svc/argocd-server 8080:80
# Then open http://localhost:8080 in your browser and log in with:
- Username: admin
- Password: (the string printed in step 1)


- In order to access the application on the browser:-
# Port forward the web-app service
kubectl port-forward svc/web-app 8081:80
# On browser
http://localhost:8081

- Again make the changes to code, in order to see the automated build and deploy in action:-
# app/index.html line 39
# change to V2
Ship faster with <span class="grad">GitOps-driven</span> cloud delivery V2
# Commit and push the changes
git add app/index.html
git commit -m "trigger CI"
git push
- Again the workflow will trigger automatically, build a new image, push it to ECR and update the tag in values-app.yaml which will be detected by ArgoCD:-
- The version now deployed is V2

- In order to revert this, fetch the previous commit ID and revert to it:-
# Check which commit you want to go back to
git log --oneline -10 origin/main
# check for the last deployed image tag and the corresponding commit ID

# Revert
git revert 7fe3bc3 --no-edit && git push
- The UI would show the previous build version V1.
Key Takeaways:-
- Splitting CI and CD is the best decision one can make. GitHub Actions builds the image and writes the tag back to Git whereas ArgoCD deploys it. Neither knows about the other, and that’s exactly why it works.
- OIDC is the saviour. No AWS_ACCESS_KEY_ID secret in the repo, no rotated keys, no leaked credentials in CI logs.
- No rollback job in CI, no “find the old image tag” , no special procedure.
Follow me for more such technical articles on DevOps
Connect with me on LinkedIn as well.
메타데이터
- post_id
- a452dc5752d3
- slug
- gitops-ship-microservices-with-github-actions-and-argocd-a452dc5752d3
- url
- https://awstip.com/gitops-ship-microservices-with-github-actions-and-argocd-a452dc5752d3
- canonical_url
- https://awstip.com/gitops-ship-microservices-with-github-actions-and-argocd-a452dc5752d3
- author_url
- https://medium.com/@shashwattripathi11
- status
- ok
- fetched_at
- 2026-07-10 13:01:02