# Naveen Gumaste - Full Knowledge Base & Articles > Portfolio, project catalog, and published engineering articles by Naveen Gumaste. > DevOps engineer and web developer based in Karnataka, India. > Canonical URL: https://naveengumaste.me > Contact: contact@naveengumaste.me | GitHub: https://github.com/NaveenGumaste | LinkedIn: https://www.linkedin.com/in/naveenkumar-gumaste/ | X: https://x.com/Z0D404 This document contains the complete, uncompressed textual content of Naveen Gumaste's portfolio, featured software projects, and all published engineering blog articles. --- ## Table of Contents 1. Profile & Core Competencies 2. Work Experience 3. Featured Projects 4. Complete Published Articles - Why Your AI Agent Keeps Getting Dumber (And How I Cut 80% of the Token Waste) - From git push to EC2: a CI/CD pipeline on AWS - Why Semantic HTML Matters: Breaking the
Habit --- ## 1. Profile & Core Competencies Naveen Gumaste is a DevOps engineer and web developer focused on building fast, accessible web applications and reliable cloud delivery workflows. Core competencies include: - **Frontend**: React 19, TypeScript, Tailwind CSS, Vite, TanStack Router, Zustand, Responsive & Accessible UI. - **Backend & Cloud**: Node.js, AWS (EC2, ECR, SSM, VPC, IAM, OIDC), Terraform, Docker, Cloudflare, Vercel. - **DevOps & CI/CD**: GitHub Actions, pull-based automated delivery, container registry pipelines, security & secret scanning. - **AI Engineering**: Token optimization, agent skill architectures (Claude Code, Cursor), progressive disclosure prompt engineering. --- ## 2. Work Experience ### Software Engineering Intern — Congle Innovations Pvt. Ltd *Duration: Feb 2025 – May 2025 | Karnataka, India* - Built the frontend architecture for the CongleX Events ticketing platform using React, TypeScript, and Tailwind CSS. - Implemented high-fidelity UI from Figma specifications, focusing on mobile responsiveness and performance. - Established component-driven design patterns with Zustand state management. - Collaborated with backend developers through structured Git pull request workflows and code reviews. --- ## 3. Featured Projects ### KeySkills - **Slug**: keyskills - **URL**: https://naveengumaste.me/projects/keyskills - **Summary**: The essential portable skill suite for high-agency AI agents: SEO setup, anti-slop frontend design, git hygiene, and codebase cleanup. ### KairoCal - **Slug**: kairocal - **URL**: https://naveengumaste.me/projects/kairocal - **Summary**: High-precision charge reconciliation and transparent tax math built for Indian equity, F&O, commodity, and currency traders. Compare charges across brokers side-by-side. ### UniTools - **Slug**: unitools - **URL**: https://naveengumaste.me/projects/unitools - **Summary**: Free, private, and powerful suite of developer utilities and converters where 100% of processing happens locally in your browser with zero server uploads or tracking. ### Caravan - **Slug**: caravan - **URL**: https://naveengumaste.me/projects/caravan - **Summary**: An interactive, nostalgic Indian cassette player and audio experience bringing vintage tape aesthetic, mechanical cassette deck animations, and iconic playlists to life. ### CryptoZ - **Slug**: cryptoz - **URL**: https://naveengumaste.me/projects/cryptoz - **Summary**: Real-time cryptocurrency market tracker providing live price streams, comprehensive coin metrics, interactive historical charts, and personalized portfolio tracking. ### Orange Finance - **Slug**: orange-finance - **URL**: https://naveengumaste.me/projects/orange-finance - **Summary**: Intuitive personal finance management app designed to track daily income and expenses, organize custom spending categories, and visualize cash flow trends. --- ## 4. Complete Published Articles ### From git push to EC2: a CI/CD pipeline on AWS - **Canonical URL**: https://naveengumaste.me/blog/devops-pipeline - **Published**: 2026-08-27T12:00:00.000Z - **Description**: How I ship KairoDash: Terraform sets up AWS, GitHub Actions tests and deploys, ECR stores the image, and OIDC plus SSM replace long-lived keys and SSH. **Context** kairo Operations (KairoDash) is a dashboard for Indian markets and portfolios, built with Next.js and Appwrite. It watches market events, filings, and news, syncs holdings from brokers like Zerodha, and sends Discord alerts from background jobs. KairoDash uses a pull path: GitHub Actions builds a container and tests it, Amazon ECR stores that image, and an ARM64 EC2 host pulls it and runs it. Terraform creates the AWS setup, Systems Manager runs the deploy, and GitHub OIDC is how GitHub logs in to AWS. The rule is simple. A push on the deploy branch should put a good image on the host, and if the image is bad the job should fail first, with no long-lived AWS keys in GitHub and no SSH key in the workflow. ![KairoDash path from GitHub Actions to ECR, SSM, and EC2. Terraform sits under AWS.](https://media.naveengumaste.me/blog/Devops-pipeline/1.webp) *Actions writes a tested image. Terraform owns the AWS setup. The host only pulls and runs.* ## Architecture The system has three parts, and each one has a clear job. **Platform.** Terraform creates the VPC, IAM, ECR, OIDC, and the EC2 instance. Ansible only sets up software on the host after the host exists. AWS resources go in Terraform, and software on the machine goes in Ansible or in the container. **GitHub Actions.** This is the only place that creates a new app version. It checks the app, builds `linux/arm64`, tests the image, pushes it to ECR, then asks SSM to update the host. It does not build on the server. **The host.** EC2 runs Docker. The instance can pull from ECR and talk to SSM, but it cannot push a new tag. The host only runs images, and it does not build them. That split is the architecture, and the pipeline is how one image moves through it. The thing we ship is the Docker image, tagged with the git SHA of the commit that made it. CI writes it, ECR is the list of images that may run, and EC2 does not get a zip file, a `git pull`, or a build folder. If I need to go back, the old SHA is still in ECR. The path is simple: commit, image, tag, registry, then the host, and the server never rebuilds it. Failures stop at the right step. App checks fail in GitHub before Docker, a bad image fails the health test before ECR, login fails before SSM, and a broken container fails the deploy job after the pull. Each step has one reason to stop, which is easier than one big "deploy" button. ### Why pull-based In a push deploy, GitHub writes to the host, which can mean `git pull`, a zip file, or `docker save` over SSH. The server becomes a place to copy files, port `22` has to stay open for that, and then "what is running" is whatever the last job copied there. Pull does the opposite. CI writes a tested image to a registry, the host logs in as itself and downloads a named tag, and GitHub does not copy files onto the machine. The workflow can still send an SSM command to start the update, but that command is a signal, not the image. To go back, the host pulls an old SHA that is already in ECR. If someone takes over the workflow they can push a tag, but they cannot run commands on the box. ### Why Amazon ECR The app runs in an AWS account, so the image store should live in that same account and use the same IAM that OIDC and the instance already use. Docker Hub would need a password on the host, and GitHub Container Registry would need a GitHub token in AWS, so login for pull and login for deploy would be two systems. A zip file in S3 is a homemade registry with no layer cache, and you would not deploy by image digest. ECR pull uses IAM, images stay private, and they live in the same AWS account as the VPC and the instance. The GitHub role can `PutImage`. The instance role cannot. ### Why SHA tags, not `latest` `latest` keeps changing, so two deploys can both use it, and rollback then means "whatever `latest` was before," which the registry no longer points to. The commit is the change, so I tag the image with that SHA and the registry entry is that commit. The host runs `kairodash:`, which CI already started and tested. Rollback is `docker pull` of an old SHA still in ECR, not a rebuild on the server and not a guess at `latest`. ## Platform I did not click this together in the AWS console. I need to be able to rebuild it even after a bad apply, so the EC2 host is a Terraform resource like the VPC and the registry, not a one-off box I would have to recreate by hand. The AWS setup is small on purpose: - Custom VPC, public subnet, internet gateway, route table - Security group: `3000` for the app, SSH locked down - `t4g.micro` (Graviton / ARM64) - ECR repository - IAM roles for GitHub Actions and for the instance - GitHub as an OIDC identity provider - SSM on the instance ![AWS VPC for KairoDash: public subnet, t4g.micro EC2, ECR, SSM, and GitHub OIDC.](https://media.naveengumaste.me/blog/Devops-pipeline/2.webp) *One VPC, one public subnet, one host. The instance pulls from ECR and accepts SSM.* A public subnet and one host is a choice, not a design for a multi-AZ service. For this app it is enough: one container, one health URL, and one place to think about the network. Traffic goes to the internet gateway, then the subnet, then the instance, then the app on port `3000`. ARM64 is a rule because the instance is Graviton and GitHub runners are x86. The image must be `linux/arm64`, or the host will pull something it cannot run. QEMU on the runner lets that x86 machine build and start an arm64 image, so the smoke test runs the same architecture the host will pull. The image architecture has to match the host, even when the builder does not. ```hcl resource "aws_instance" "kairo" { ami = data.aws_ami.amazon_linux.id instance_type = "t4g.micro" subnet_id = aws_subnet.kairo_public.id vpc_security_group_ids = [aws_security_group.kairo_ec2.id] associate_public_ip_address = true iam_instance_profile = aws_iam_instance_profile.kairo_ec2.name } ``` I save a plan first, review it, then apply that same plan instead of applying a new one at the last second. ```bash terraform plan -out=tfplan ``` ```bash terraform apply tfplan ``` IAM has two roles because the jobs are different. The GitHub Actions role logs in to ECR and pushes, and the instance role pulls that image and uses SSM. CI does not need `ssm:SendCommand` on every machine, and the host does not need `ecr:PutImage`. The GitHub role can only push to this registry, and only from this repo: ```hcl statement { sid = "ECRPushKairoDash" effect = "Allow" actions = [ "ecr:BatchCheckLayerAvailability", "ecr:CompleteLayerUpload", "ecr:InitiateLayerUpload", "ecr:PutImage", "ecr:UploadLayerPart" ] resources = [ aws_ecr_repository.kairo.arn ] } ``` That is how permission works: GitHub Actions writes images and EC2 reads them, so if one role is stolen the damage stays on that side of the registry. State is a local file today, which is fine for one person and one environment. It is not fine if two applies can run at once, or if that file is the only map of production. Terraform state maps names in the config to real AWS IDs and can also hold secrets, so remote state with locking comes before a second environment. ## Pipeline A push does not start with Docker. The app has to build first. The workflow checks out the repo, installs with Bun, typechecks, builds, then lints. Those steps are fast compared with a registry push, and they catch errors that should never become an image. Only then does the runner build the container, and it does not publish yet. The runner is x86, so QEMU emulates ARM and Buildx builds `linux/arm64`. `push: false` and `load: true` keep that image on the runner so the next step can start the same architecture the host will run. ```yaml - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build Docker image uses: docker/build-push-action@v7 with: context: . file: ./Dockerfile platforms: linux/arm64 push: false load: true tags: kairodash:ci ``` A green build means the image files were created, not that the app is listening. The runner starts the image locally and hits the same health URL the host will use later. ```bash docker run -d \ --name kairodash-ci \ -p 3000:3000 \ kairodash:ci ``` ```bash curl --fail --silent http://localhost:3000/api/health ``` If that fails, the job prints logs and stops, so ECR never sees that tag. This check matters because deploy here is pull and replace: stop the old container and start the new one. If the new image is dead, CI should fail while the old version is still running. Images that pass are not tagged `latest`, because `latest` keeps changing. The tag is the git SHA, so the registry name is a commit. ```yaml env: ECR_REPOSITORY: kairodash IMAGE_TAG: ${{ github.sha }} ``` ```bash docker tag \ kairodash:ci \ $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG docker push \ $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG ``` The deploy command uses that tag, the host runs what CI already started, and rollback is another pull of a SHA that is still in ECR, not a guess at old `latest`. ## Identity GitHub does not store `AWS_ACCESS_KEY_ID`. The job asks for an OIDC token, AWS STS checks it against the GitHub identity provider on the account, and the workflow assumes a role for the job. ```yaml permissions: contents: read id-token: write ``` `id-token: write` is not AWS access. It only lets the job ask for the token. The IAM trust policy is the real lock: this repository, and this branch if I add that. A workflow in another repo cannot assume the role just because it also runs on GitHub Actions. Then the job trades that token for a short AWS session, limited to the account in GitHub variables: ```yaml - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v6.2.3 with: role-to-assume: ${{ vars.AWS_ROLE_ARN }} aws-region: ${{ vars.AWS_REGION }} allowed-account-ids: ${{ vars.AWS_ACCOUNT_ID }} ``` The credentials last for the job, then they go away. Role ARN and region come from GitHub variables, not from a long-lived key in secrets. GitHub proves who it is and IAM decides what it can do. ECR push is on the GitHub role and ECR pull is on the instance, the same split as the Terraform policy. AWS checks this when the role is assumed, and there is no long-lived key. ## Deploy Once the image is in ECR, the host must run that tag instead of rebuilding the same commit. ![Deploy without SSH: GitHub Actions uses IAM and SSM so EC2 can pull and replace the container.](https://media.naveengumaste.me/blog/Devops-pipeline/4.webp) *IAM and SSM are the deploy path. The host pulls the SHA. There is no SSH key in GitHub.* The workflow assumes the deploy role and sends an SSM command to the instance: 1. Log in to ECR with the instance profile 2. Pull `kairodash:` 3. Stop the current container 4. Start the new one 5. Check `/api/health` on the host I use SSM because the agent is already on the instance and the instance role already allows it, so GitHub never stores an SSH key. Port `22` can stay closed except for admin use. Deploy uses IAM and the agent, which is the same login model as the rest of the account. The host does not get source code or install build tools. It pulls a tag that CI already started, so the server stays small and what is running is what was tested. ## Destroy is not stop I used `terraform destroy` to turn the instance off for the night, which was wrong. I wanted the server off, but I deleted the whole setup. ```bash terraform plan -destroy -out=tfplan-destroy ``` ```bash terraform apply tfplan-destroy ``` Terraform removed the VPC, IAM, ECR, the OIDC provider, and the instance. The next Actions run failed on OIDC: ```text Could not assume role with OIDC: The web identity token provided could not be validated. ``` The error is true, but it points at the wrong thing. The token path was fine; the identity provider and the role were gone, and the log named a missing piece of AWS. ![Terraform destroy: AWS setup gone, CI failing, then recovery with plan and apply.](https://media.naveengumaste.me/blog/Devops-pipeline/5.webp) *Destroy is not a power button. I brought it back the same way I created it: plan, then apply.* I did not rebuild in the console, because that would leave Terraform talking about AWS that no longer exists, or AWS holding things Terraform does not know. The config is what I trust, so I recovered the same way I create things: save a plan, then apply that plan. ```bash terraform plan -out=tfplan ``` ```bash terraform apply tfplan ``` The instance came back with a new ID, which is expected. Restoring `terraform.tfstate.backup` would only change what Terraform thinks is there, but AWS was empty. An old state file does not create resources again; it makes config, state, and AWS disagree. Those three have to match: - **Config** is what I want - **State** is what Terraform last saw - **AWS** is what is there now If the job fails, I check that chain before I debug the service named in the error: does it exist, then account, role, trust, permission, then can I reach it. ## What I would change This path is enough for one app and one environment. It is not a DEV and PROD setup yet. ![Next steps: split DEV and PROD, lock remote state, and protect production.](https://media.naveengumaste.me/blog/Devops-pipeline/6.webp) *Next: split DEV and PROD, lock remote state, and protect production. Not another tool in the middle.* Next I want a real DEV and PROD, remote state with locking, and a protected apply to production. The shape stays the same: OIDC, SHA tags, health tests before push, and SSM pull on the host. What is missing is a split between environments and control of changes, not another tool in the middle. * *Note.* This is the setup I have now, not a finished production design. There are gaps I already see, and it can be improved. I built it with the knowledge I have, and I keep improving it. --- ### Why Semantic HTML Matters: Breaking the
Habit - **Canonical URL**: https://naveengumaste.me/blog/why-semantic-html-matters - **Published**: 2026-06-30T15:20:00.000Z - **Description**: Stop nesting divs. Discover how semantic tags build a better web for screen readers, search engines, and fellow developers. ## The Problem: Div Soup If you inspect almost any modern website, you will likely see nested layers of `
` tags going ten levels deep. This is often referred to as "div soup." While a `
` is great for styling, it holds absolutely zero meaning. To a browser, a search engine crawler, or a screen reader, a `
` is just an empty container. ## What is Semantic HTML? The word **semantic** means "relating to meaning in language." In web development, semantic HTML refers to using tags that clearly describe their meaning to both the browser and the developer. Instead of writing this: ```html
``` We write this: ```html
``` ## Three Reasons Why Semantic HTML is Required ### 1. Accessibility (a11y) For users with visual impairments who rely on screen readers, semantic tags act as landmarks. A screen reader can jump straight to "navigation" or "main content" regions instead of reading everything top to bottom. Without semantic tags, users lose that quick-navigation shortcut and must tab through content sequentially. ### 2. Search Engine Optimization (SEO) Search engine bots (like Googlebot) are automated readers. When they parse your portfolio, they prioritize content inside `
`, `
`, and heading tags. By using semantic tags, you tell search engines exactly what the most important content on your page is, which directly boosts your SEO ranking. ### 3. Maintainability Code is read far more often than it is written. Looking at a file structured with `
`, ``, and `