<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Atul Vishwakarma</title>
    <description>The latest articles on DEV Community by Atul Vishwakarma (@vatul16).</description>
    <link>https://dev.to/vatul16</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F530617%2F3e6b3efc-a674-477c-be19-95c2214adbf3.jpg</url>
      <title>DEV Community: Atul Vishwakarma</title>
      <link>https://dev.to/vatul16</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vatul16"/>
    <language>en</language>
    <item>
      <title>The Google File System Explained: How Google Built Storage That Expects to Fail</title>
      <dc:creator>Atul Vishwakarma</dc:creator>
      <pubDate>Fri, 21 Aug 2026 02:30:00 +0000</pubDate>
      <link>https://dev.to/vatul16/the-google-file-system-explained-how-google-built-storage-that-expects-to-fail-4741</link>
      <guid>https://dev.to/vatul16/the-google-file-system-explained-how-google-built-storage-that-expects-to-fail-4741</guid>
      <description>&lt;p&gt;In 2003, three Google engineers — Sanjay Ghemawat, Howard Gobioff, and Shun-Tak Leung — published a paper describing the storage system they'd built to keep up with Google's data. It's called &lt;strong&gt;The Google File System&lt;/strong&gt; (GFS), and it went on to influence nearly every large-scale storage system that came after it, most directly the Hadoop Distributed File System (HDFS).&lt;/p&gt;

&lt;p&gt;This is the first post in a series where I break down classic systems papers that quietly shaped how we build infrastructure today. We'll follow the same structure every time, so you always know what to expect. Today: GFS.&lt;/p&gt;

&lt;p&gt;You can read the original paper here: &lt;a href="https://research.google.com/archive/gfs-sosp2003.pdf" rel="noopener noreferrer"&gt;The Google File System (PDF)&lt;/a&gt;. All credit for the design and ideas below belongs to the original authors and Google.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem Statement
&lt;/h2&gt;

&lt;p&gt;By the early 2000s, Google was crawling, indexing, and processing the web — which meant generating and chewing through enormous volumes of data across a lot of cheap machines. They needed a file system that could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Store many terabytes (eventually petabytes) of data reliably.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run on inexpensive, commodity hardware rather than expensive specialized storage arrays.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stay available and correct even though individual machines would constantly fail.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Support workloads that mostly &lt;em&gt;appended&lt;/em&gt; data rather than randomly overwriting it — think log files, crawl results, and intermediate computation output.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Serve hundreds of clients concurrently with high throughput, without needing every read or write to be blazingly low-latency.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No off-the-shelf file system at the time was designed around these specific realities. So Google built its own.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the Older Approach Struggled
&lt;/h2&gt;

&lt;p&gt;Traditional file systems — including distributed ones like AFS at the time — were built around a different set of assumptions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Failure was treated as an exception&lt;/strong&gt;, not a routine event. Systems assumed disks and machines mostly worked, and failure handling was bolted on rather than core to the design.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Files were assumed to be relatively small&lt;/strong&gt;, and systems optimized for fine-grained I/O rather than huge, multi-gigabyte files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Random writes were a first-class use case.&lt;/strong&gt; Filesystems were built to support arbitrary overwrites anywhere in a file, which added complexity that Google's workloads didn't actually need.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Client-side caching of file data&lt;/strong&gt; was a default assumption, which brings cache-coherence complexity — but Google's workloads mostly streamed through huge files once, so caching data blocks bought little.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;POSIX compliance&lt;/strong&gt; was treated as a requirement, which constrains the API and adds overhead that isn't always earning its keep for a specialized internal system.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this made older systems "wrong" — they were solving a different problem. But applied directly to Google's scale and access patterns, these assumptions became a poor fit. So instead of retrofitting an existing file system, Google's engineers designed one from scratch around their actual workload.&lt;/p&gt;




&lt;h2&gt;
  
  
  Core Architecture
&lt;/h2&gt;

&lt;p&gt;At a high level, a GFS cluster has three kinds of participants: a single &lt;strong&gt;master&lt;/strong&gt;, many &lt;strong&gt;chunkservers&lt;/strong&gt;, and the &lt;strong&gt;clients&lt;/strong&gt; that read and write data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgrhwz45vhw4k3e05phh1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgrhwz45vhw4k3e05phh1.png" alt=" " width="800" height="691"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Master&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
One master process holds all the metadata in memory: the file namespace, which files map to which chunks, and where those chunks currently live. Crucially, the master is only involved in &lt;em&gt;control&lt;/em&gt; decisions — it tells clients where to find data, but it never touches the actual bytes being read or written. That separation is what keeps it from becoming a bottleneck even as the cluster scales.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chunkservers&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Files are split into fixed-size 64 MB chunks, and each chunk is replicated (three copies, by default) across different chunkservers and different racks. Chunkservers just store chunks as regular files on Linux and serve reads/writes for the byte ranges clients ask for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clients&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A client asks the master "which chunkserver holds this chunk?", caches that answer briefly, and then talks directly to the relevant chunkserver for the actual data transfer. Metadata traffic and data traffic are cleanly split.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why 64 MB chunks?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
This is one of the more counterintuitive design choices, and it's worth calling out on its own. A large chunk size means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Clients need to talk to the master far less often, since one chunk covers a lot of data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The total number of chunks — and therefore the size of the master's in-memory metadata — stays manageable even at huge scale.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sequential reads and writes, which dominate Google's workload, get to be highly efficient.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Writes and leases&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
For mutations, the master grants a temporary "lease" to one replica, making it the &lt;em&gt;primary&lt;/em&gt; for that chunk. The primary decides the order in which concurrent writes are applied, and the other replicas follow that same order. Data itself is pushed along a pipelined chain between chunkservers — not broadcast from the client to every replica at once — which makes better use of available network bandwidth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Record append&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
GFS added an operation not found in traditional file systems: atomic record append. Multiple clients can append to the same file concurrently, and GFS guarantees each individual append lands atomically somewhere in the file — without the clients needing any external locking. This turned out to be a great fit for producer-consumer style workloads, like many worker processes writing results into one shared output file.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Trade-offs
&lt;/h2&gt;

&lt;p&gt;GFS makes almost none of its decisions for free — every design win is paired with something given up:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;th&gt;What you gain&lt;/th&gt;
&lt;th&gt;What you give up&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Single master&lt;/td&gt;
&lt;td&gt;Simple design, easy global coordination, easy to reason about&lt;/td&gt;
&lt;td&gt;A single point that must be carefully protected (via replication and fast recovery) from becoming a bottleneck or outage source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large (64 MB) chunks&lt;/td&gt;
&lt;td&gt;Less master traffic, smaller metadata footprint, efficient sequential I/O&lt;/td&gt;
&lt;td&gt;Small files or hot files can create load imbalance ("hot spots") on the few chunkservers that hold them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Relaxed consistency model&lt;/td&gt;
&lt;td&gt;Simpler, faster implementation; no expensive coordination on every write&lt;/td&gt;
&lt;td&gt;Applications must be written defensively — using checksums, unique record IDs, and append-friendly patterns — to tolerate duplicate or padded data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No client-side data caching&lt;/td&gt;
&lt;td&gt;No cache-coherence complexity&lt;/td&gt;
&lt;td&gt;Every read of "hot" data goes back to a chunkserver rather than a local cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Optimized for append-heavy, sequential workloads&lt;/td&gt;
&lt;td&gt;Excellent throughput for its target use case&lt;/td&gt;
&lt;td&gt;Not a good general-purpose file system — small random writes are supported but never optimized&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The overarching theme: GFS repeatedly chose &lt;em&gt;simplicity and throughput for a specific known workload&lt;/em&gt; over generality. That's a defensible trade only because Google controlled both the file system and the applications running on it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Failure Cases or Limitations
&lt;/h2&gt;

&lt;p&gt;The paper is refreshingly honest about where the system falls short:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Single-client write throughput was disappointing.&lt;/strong&gt; The paper reports it was roughly half of the theoretical network limit, largely because of how their networking stack interacted with the pipelined replication scheme.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Small files can create hot spots.&lt;/strong&gt; If a single-chunk file (like an executable pushed to hundreds of machines at once) suddenly gets many concurrent readers, the few chunkservers holding it can get overloaded. Google's real-world fix was operational — higher replication for such files and staggered rollout — rather than architectural.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The relaxed consistency model isn't free for application developers.&lt;/strong&gt; Because concurrent writes can leave a region "consistent but undefined" (all replicas agree, but the content may be a merge of multiple writers), applications must be written defensively — self-validating records, checksums, and idempotency-friendly identifiers become mandatory, not optional.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The master is a structural bottleneck risk.&lt;/strong&gt; Even with fast restart and log replication, one process holding all metadata in memory sets a ceiling on file count and requires real engineering (checkpointing, prefix-compressed namespace, binary search over metadata) to keep operations fast at scale.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It is explicitly not POSIX-compliant and not general-purpose.&lt;/strong&gt; GFS was tuned tightly to Google's own workloads. Bolting it onto a very different access pattern (lots of small random writes, for instance) would likely perform poorly.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What Modern Engineers Can Learn
&lt;/h2&gt;

&lt;p&gt;Even if you'll never build a file system, several ideas from this paper show up constantly in modern distributed systems design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Design for the workload you actually have&lt;/strong&gt;, not a hypothetical general one. GFS's biggest wins came from refusing to support use cases (random writes, POSIX semantics) that weren't part of the real workload.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Separate control plane from data plane.&lt;/strong&gt; The master handles metadata and coordination; chunkservers handle the actual bytes. This pattern — keep the "brain" out of the high-volume data path — recurs everywhere from Kubernetes to modern object stores.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Treat failure as the default state, not an edge case.&lt;/strong&gt; Fast recovery, replication, and constant background repair aren't bolted on after the fact; they're central to the design from day one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Relaxing a guarantee can be a legitimate design choice&lt;/strong&gt; — but only if you push the resulting complexity to a place where it can be handled well (in this case, onto application-level conventions like checksums and idempotent writers).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A single coordinator isn't automatically a scalability bug.&lt;/strong&gt; If you keep it out of the hot path and make it fast to recover, a centralized component can radically simplify a system without becoming its bottleneck.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How This Maps to AWS / Kubernetes / DevOps
&lt;/h2&gt;

&lt;p&gt;If you work with modern cloud infrastructure, you've already used GFS's descendants, even if the vocabulary is different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Amazon S3&lt;/strong&gt; mirrors the core GFS idea: a durable, replicated, distributed object store optimized for large sequential objects rather than small random-access files, with a metadata layer that's kept separate from the actual data path.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HDFS&lt;/strong&gt;, the storage layer behind much of the original Hadoop ecosystem, is a near-direct open-source implementation of GFS's ideas: a NameNode (the master) and DataNodes (the chunkservers), large fixed-size blocks, and replication for fault tolerance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Kubernetes' control plane vs. data plane split&lt;/strong&gt; echoes the same architectural instinct: the API server and etcd (control plane) coordinate and store metadata/desired state, while the actual application traffic flows directly between pods, never through the control plane.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Kafka's partition replication and leader election&lt;/strong&gt; resemble GFS's primary/lease mechanism — one replica is elected to order writes for a period, and others follow that order, with automatic re-election on failure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DevOps takeaway:&lt;/strong&gt; when you design internal tooling or services, ask the same question GFS's authors asked: what does our actual workload look like, and which "standard" guarantees can we safely relax to buy simplicity and throughput? That question — more than any specific technology — is the real lesson of this paper.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Next in this series: MapReduce (Dean &amp;amp; Ghemawat, 2004).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Source: Ghemawat, S., Gobioff, H., &amp;amp; Leung, S-T. "The Google File System." SOSP '03.&lt;/em&gt; &lt;a href="https://research.google.com/archive/gfs-sosp2003.pdf" rel="noopener noreferrer"&gt;&lt;em&gt;Original PDF&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>architecture</category>
      <category>distributedsystems</category>
      <category>google</category>
    </item>
    <item>
      <title>What I’ve Learned Building a CI/CD Cloud Pipeline from Scratch</title>
      <dc:creator>Atul Vishwakarma</dc:creator>
      <pubDate>Fri, 14 Aug 2026 02:30:00 +0000</pubDate>
      <link>https://dev.to/vatul16/what-ive-learned-building-a-cicd-cloud-pipeline-from-scratch-ol8</link>
      <guid>https://dev.to/vatul16/what-ive-learned-building-a-cicd-cloud-pipeline-from-scratch-ol8</guid>
      <description>&lt;p&gt;When I first began my transition from a Full Stack Developer to focusing heavily on Cloud &amp;amp; DevOps, the learning curve felt like an absolute mountain.&lt;/p&gt;

&lt;p&gt;Between managing the infrastructure for thousands of daily orders and squeezing in certification study time during my 3.5-hour daily commute on the Mumbai local trains, my overarching goal was simple: &lt;strong&gt;stop doing things manually.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Over the last few months, I have documented every step of this journey right here on &lt;a href="https://atulcodes.hashnode.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;Atul Codes&lt;/strong&gt;&lt;/a&gt;. From writing my first Dockerfile to provisioning cloud infrastructure with code, it has been an incredible phase of growth.&lt;/p&gt;

&lt;p&gt;Here is a look back at the biggest lessons I’ve learned while building a modern CI/CD cloud pipeline from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1: Escaping "It Works on My Machine"
&lt;/h2&gt;

&lt;p&gt;The first major bottleneck I had to solve was environment inconsistency. Realizing that manual server configurations were slowing down our development cycles pushed me straight into containerization.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Learning Docker:&lt;/strong&gt; I started by figuring out &lt;a href="https://www.google.com/search?q=https://atulcodes.hashnode.dev/dockerize-nodejs-app-beginner-tutorial" rel="noopener noreferrer"&gt;How to Dockerize a Node.js App&lt;/a&gt;. Packaging the runtime and the code together changed how I viewed deployments entirely.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Power of Automation:&lt;/strong&gt; But building containers manually wasn't enough. The real "aha!" moment came when I learned to &lt;a href="https://www.google.com/search?q=https://atulcodes.hashnode.dev/automate-your-docker-builds-with-github-actions" rel="noopener noreferrer"&gt;Automate Docker Builds with GitHub Actions&lt;/a&gt;, turning code commits into instant, deployable images.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Embracing the Terminal:&lt;/strong&gt; Of course, containerization comes with its own headaches. I spent hours debugging until I learned &lt;a href="https://www.google.com/search?q=https://atulcodes.hashnode.dev/fix-docker-port-already-in-use-error" rel="noopener noreferrer"&gt;How to Fix the "Port Already in Use" Error&lt;/a&gt;, and I quickly realized that mastering the &lt;a href="https://www.google.com/search?q=https://atulcodes.hashnode.dev/5-linux-bash-commands-devops-troubleshooting" rel="noopener noreferrer"&gt;5 Linux Bash Commands Every DevOps Engineer Uses Daily&lt;/a&gt; was non-negotiable for troubleshooting headless servers.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Phase 2: Killing "ClickOps" with Infrastructure as Code
&lt;/h2&gt;

&lt;p&gt;Once the applications were containerized, I needed a place to run them. The AWS Management Console is great for learning, but clicking through UI menus to spin up servers is dangerous for production.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Entering the Terraform Ecosystem:&lt;/strong&gt; I forced myself to learn HashiCorp Configuration Language (HCL). Writing the code to &lt;a href="https://www.google.com/search?q=https://atulcodes.hashnode.dev/terraform-aws-ec2-iac-tutorial" rel="noopener noreferrer"&gt;Provision an AWS EC2 Instance with Terraform&lt;/a&gt; was a massive milestone. It took away the fear of breaking the cloud because everything was version-controlled.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Building a Second Brain:&lt;/strong&gt; As I studied deeper for my Terraform Associate certification, the syntax became overwhelming. To save time, I compiled my &lt;a href="https://www.google.com/search?q=https://atulcodes.hashnode.dev/ultimate-terraform-cheatsheet-devops" rel="noopener noreferrer"&gt;Ultimate Terraform Cheatsheet for DevOps Engineers&lt;/a&gt;—which has become my most used daily reference.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Optimizing the Workflow:&lt;/strong&gt; To write all this HCL effectively, I had to upgrade my environment. Finding the &lt;a href="https://www.google.com/search?q=https://atulcodes.hashnode.dev/top-5-vs-code-extensions-devops-cloud-engineering" rel="noopener noreferrer"&gt;Top 5 VS Code Extensions for DevOps and Cloud Engineering&lt;/a&gt; completely streamlined my development process.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Phase 3: The Reality of Production &amp;amp; Security
&lt;/h2&gt;

&lt;p&gt;Building infrastructure is fun, but securing it is what actually keeps you employed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Securing the Pipeline:&lt;/strong&gt; Early on, I realized the immense danger of leaked keys. I immediately audited my workflows and documented &lt;a href="https://www.google.com/search?q=https://atulcodes.hashnode.dev/stop-hardcoding-aws-credentials-security-guide" rel="noopener noreferrer"&gt;How to Stop Hardcoding AWS Credentials&lt;/a&gt; using environment variables and AWS CLI profiles.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Protecting the Wallet:&lt;/strong&gt; The cloud is cheap until you make a typo. Setting up &lt;a href="https://www.google.com/search?q=https://atulcodes.hashnode.dev/how-to-set-up-aws-billing-alerts-budgets" rel="noopener noreferrer"&gt;AWS Billing Alerts&lt;/a&gt; gave me the peace of mind to experiment with new architectures without the fear of a $1,000 surprise bill.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Exploring the Future:&lt;/strong&gt; With security locked down, I started exploring beyond traditional servers, successfully &lt;a href="https://www.google.com/search?q=https://atulcodes.hashnode.dev/deploy-first-serverless-function-aws-lambda" rel="noopener noreferrer"&gt;Deploying My First Serverless Function on AWS Lambda&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4caa42i1msd18v86kvmg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4caa42i1msd18v86kvmg.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What’s Next for Atul Codes?
&lt;/h2&gt;

&lt;p&gt;Writing these articles over the last quarter hasn't just been about sharing knowledge—it has been the ultimate forcing function for my own upskilling. When you have to explain a complex topic clearly to someone else, you uncover the gaps in your own understanding.&lt;/p&gt;

&lt;p&gt;So, what is the next big goal?&lt;/p&gt;

&lt;p&gt;I am currently diving deep into AWS ECS Fargate for scalable container orchestration, and preparing to knock out my HashiCorp Terraform Associate certification. You can expect a deep-dive series on both of those topics very soon.&lt;/p&gt;

&lt;p&gt;Thank you to everyone who has read, commented, and shared my posts so far. Let’s keep building!&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;☕ &lt;strong&gt;If my tutorials have helped you on your own cloud journey, consider&lt;/strong&gt; &lt;a href="https://buymeacoffee.com/vatul16" rel="noopener noreferrer"&gt;&lt;strong&gt;buying me a coffee&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;to support the blog!&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>terraform</category>
      <category>cicd</category>
    </item>
    <item>
      <title>My Top 5 VS Code Extensions for DevOps and Cloud Engineering</title>
      <dc:creator>Atul Vishwakarma</dc:creator>
      <pubDate>Fri, 07 Aug 2026 02:30:00 +0000</pubDate>
      <link>https://dev.to/vatul16/my-top-5-vs-code-extensions-for-devops-and-cloud-engineering-4gdg</link>
      <guid>https://dev.to/vatul16/my-top-5-vs-code-extensions-for-devops-and-cloud-engineering-4gdg</guid>
      <description>&lt;p&gt;As a Cloud &amp;amp; DevOps Engineer, Visual Studio Code isn't just a text editor—it is my absolute command center.&lt;/p&gt;

&lt;p&gt;Whether I am writing Infrastructure as Code (IaC), debugging a broken CI/CD pipeline, or managing containerized microservices, switching back and forth between a browser, a terminal, and an editor is a massive productivity killer. The right extensions bring all of that context directly into your workspace.&lt;/p&gt;

&lt;p&gt;If you are setting up your development environment on a fresh Linux install or just looking to optimize your daily workflow, here are the top 5 VS Code extensions I use every single day at &lt;strong&gt;Atul Codes&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. HashiCorp Terraform
&lt;/h2&gt;

&lt;p&gt;If you are writing Terraform modules to provision cloud infrastructure, this extension is non-negotiable.&lt;/p&gt;

&lt;p&gt;Writing HCL (HashiCorp Configuration Language) without syntax highlighting is painful. The official HashiCorp extension provides auto-completion for resource blocks, real-time syntax validation, and instantly formats your code when you save.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I love it:&lt;/strong&gt; It highlights missing required variables before I even run &lt;code&gt;terraform validate&lt;/code&gt; or &lt;code&gt;terraform plan&lt;/code&gt;, saving me countless failed deployment attempts.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fai5p9ripgnxr0ew8gk6q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fai5p9ripgnxr0ew8gk6q.png" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. Docker
&lt;/h2&gt;

&lt;p&gt;When you are containerizing applications, writing Dockerfiles and managing &lt;code&gt;docker-compose.yaml&lt;/code&gt; files can get messy.&lt;/p&gt;

&lt;p&gt;The official Docker extension by Microsoft makes it incredibly easy to build, manage, and deploy containerized applications right from VS Code. It provides syntax highlighting for your Dockerfiles and gives you a visual UI in the sidebar to see exactly which containers are running and which images are taking up space on your hard drive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I love it:&lt;/strong&gt; I can right-click any running container in the sidebar and select "View Logs" to instantly troubleshoot without typing out long &lt;code&gt;docker logs &amp;lt;container-id&amp;gt;&lt;/code&gt; commands in the terminal.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8cmzhdswnr9oilgao7g8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8cmzhdswnr9oilgao7g8.png" width="700" height="941"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. YAML (by Red Hat)
&lt;/h2&gt;

&lt;p&gt;In the DevOps world, you are going to write a &lt;em&gt;lot&lt;/em&gt; of YAML. From GitHub Actions workflows to Kubernetes manifests, YAML is everywhere.&lt;/p&gt;

&lt;p&gt;The YAML extension by Red Hat is a lifesaver. It provides comprehensive auto-completion, hover support, and strict validation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I love it:&lt;/strong&gt; YAML relies entirely on exact indentation. One misplaced space will break an entire GitHub Actions pipeline. This extension catches those formatting errors instantly, so you don't have to wait for your CI/CD pipeline to fail to realize you made a typo.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. AWS Toolkit
&lt;/h2&gt;

&lt;p&gt;Since I spend the vast majority of my time architecting on AWS, having the AWS Toolkit installed is a massive time-saver.&lt;/p&gt;

&lt;p&gt;This extension lets you interact with AWS services like S3, Lambda, and CloudWatch directly from your editor. You can view your S3 buckets, invoke serverless functions locally, and even read CloudWatch logs without ever logging into the AWS Management Console.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I love it:&lt;/strong&gt; It securely integrates with my local AWS CLI profiles, meaning I can safely switch between environments without risking deploying to the wrong account.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flfoo60zvbanatk8nl5mi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flfoo60zvbanatk8nl5mi.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  5. GitLens — Git supercharged
&lt;/h2&gt;

&lt;p&gt;Git is the backbone of everything we do, but tracking down &lt;em&gt;who&lt;/em&gt; changed a specific line of infrastructure code (and &lt;em&gt;why&lt;/em&gt;) can take some digging.&lt;/p&gt;

&lt;p&gt;GitLens supercharges the built-in Git capabilities of VS Code. It adds inline blame annotations—meaning you can click on any line of code and see exactly who wrote it, when they wrote it, and the commit message associated with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I love it:&lt;/strong&gt; It makes team collaboration seamless. If a deployment script breaks, I instantly know which commit introduced the bug. &lt;em&gt;(Note: GitLens has a fantastic free version, but if you manage massive repositories and need advanced visual commit graphs).&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;Your code editor should work &lt;em&gt;for&lt;/em&gt; you, not against you. By loading up VS Code with the right tools, you can catch errors earlier, type less boilerplate code, and stay focused on building resilient cloud infrastructure.&lt;/p&gt;

&lt;p&gt;Did I miss your favorite DevOps extension? Let me know in the comments below!&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;☕ &lt;strong&gt;Did you discover a new favorite extension today?&lt;/strong&gt; &lt;a href="https://buymeacoffee.com/vatul16" rel="noopener noreferrer"&gt;&lt;strong&gt;Buy me a coffee&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;to help keep Atul Codes running!&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>vscode</category>
      <category>devops</category>
      <category>terraform</category>
      <category>docker</category>
    </item>
    <item>
      <title>The 5 Linux Bash Commands Every DevOps Engineer Uses Daily</title>
      <dc:creator>Atul Vishwakarma</dc:creator>
      <pubDate>Fri, 31 Jul 2026 02:30:00 +0000</pubDate>
      <link>https://dev.to/vatul16/the-5-linux-bash-commands-every-devops-engineer-uses-daily-3m6i</link>
      <guid>https://dev.to/vatul16/the-5-linux-bash-commands-every-devops-engineer-uses-daily-3m6i</guid>
      <description>&lt;p&gt;When you are first learning cloud engineering, you spend a lot of time clicking through beautiful web dashboards.&lt;/p&gt;

&lt;p&gt;But what happens when your application crashes at 2:00 AM, the web server goes offline, and the only access you have is an SSH connection to a headless Linux box? You can't rely on a graphical user interface anymore. You need the command line.&lt;/p&gt;

&lt;p&gt;Whether you are deploying microservices or debugging a massive production outage, the Linux terminal is where the real DevOps work happens. Here are the 5 Bash commands I use daily to troubleshoot broken servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;code&gt;tail&lt;/code&gt; (Watching Live Logs)
&lt;/h2&gt;

&lt;p&gt;When an application fails, your first instinct should be to check the logs. But log files can be gigabytes in size. Opening a 2GB file in a text editor will crash your server entirely.&lt;/p&gt;

&lt;p&gt;Instead, we use &lt;code&gt;tail&lt;/code&gt; to look at just the very end of the file. By adding the &lt;code&gt;-f&lt;/code&gt; (follow) flag, you can watch the logs stream in real-time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Watch the Nginx error log live&lt;/span&gt;
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/nginx/error.log

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it saves you:&lt;/strong&gt; You can run &lt;code&gt;tail -f&lt;/code&gt;, trigger your broken API endpoint in another window, and watch the exact error message pop up instantly on your screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. &lt;code&gt;grep&lt;/code&gt; (Finding the Needle in the Haystack)
&lt;/h2&gt;

&lt;p&gt;If &lt;code&gt;tail&lt;/code&gt; is for watching logs live, &lt;code&gt;grep&lt;/code&gt; is for searching through them after the fact. &lt;code&gt;grep&lt;/code&gt; allows you to search for specific text patterns inside massive files.&lt;/p&gt;

&lt;p&gt;If a user complains that their payment failed, you don't want to read a million lines of logs. You just want to see the lines containing the word "ERROR" or "500".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Find every line containing "500 Internal Server Error"&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"500 Internal Server Error"&lt;/span&gt; /var/log/app/production.log

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Pro Tip: Add the&lt;/em&gt; &lt;code&gt;-i&lt;/code&gt; &lt;em&gt;flag to make your search case-insensitive!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. &lt;code&gt;awk&lt;/code&gt; (Parsing Structured Data)
&lt;/h2&gt;

&lt;p&gt;Sometimes &lt;code&gt;grep&lt;/code&gt; gives you too much information. If you have a log file and you &lt;em&gt;only&lt;/em&gt; want to extract a specific piece of data—like a list of IP addresses that are spamming your server—you need &lt;code&gt;awk&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;awk&lt;/code&gt; reads text line by line and splits it into columns (which it calls fields).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Print ONLY the first column (e.g., the IP address) from an access log&lt;/span&gt;
&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt; /var/log/nginx/access.log

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it saves you:&lt;/strong&gt; When paired with other commands, &lt;code&gt;awk&lt;/code&gt; allows you to quickly isolate data so you can block malicious IPs or count exactly how many times a specific user ID failed to log in.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. &lt;code&gt;chmod&lt;/code&gt; (Fixing "Permission Denied")
&lt;/h2&gt;

&lt;p&gt;The most common error you will face as a junior DevOps engineer is &lt;code&gt;Permission denied&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This usually happens because the user running your application (like &lt;code&gt;www-data&lt;/code&gt; or &lt;code&gt;ubuntu&lt;/code&gt;) does not have the right to read a configuration file or execute a deployment script. &lt;code&gt;chmod&lt;/code&gt; (change mode) fixes this by modifying the file permissions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Make a deployment script executable&lt;/span&gt;
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x deploy.sh

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it saves you:&lt;/strong&gt; When your CI/CD pipeline suddenly fails because a script won't run, a quick &lt;code&gt;chmod +x&lt;/code&gt; is usually the magic bullet.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. &lt;code&gt;htop&lt;/code&gt; (Monitoring System Resources)
&lt;/h2&gt;

&lt;p&gt;Is your server crashing because of a memory leak in your code, or because the CPU is maxed out at 100%? You need to know immediately.&lt;/p&gt;

&lt;p&gt;While &lt;code&gt;top&lt;/code&gt; comes pre-installed on most systems, &lt;code&gt;htop&lt;/code&gt; is the vastly superior, colorful, and interactive version. It provides a visual breakdown of your CPU cores, RAM usage, and exactly which processes are eating up your resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Launch the interactive resource monitor&lt;/span&gt;
htop

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it saves you:&lt;/strong&gt; One glance at &lt;code&gt;htop&lt;/code&gt; tells you if you need to quickly upgrade your server size (scale up) or if a rogue process needs to be killed immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;You don't need to be a Linux kernel expert to be a great DevOps engineer, but you do need to be comfortable in the terminal. The next time you build a project, skip the UI. Force yourself to navigate the filesystem, check the logs, and manage your scripts entirely through Bash.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;☕ &lt;strong&gt;Did this reference guide help you out?&lt;/strong&gt; &lt;a href="https://buymeacoffee.com/vatul16" rel="noopener noreferrer"&gt;&lt;strong&gt;Buy me a coffee&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;to help keep Atul Codes running!&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>linux</category>
      <category>devops</category>
      <category>bash</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Deploying Your First Serverless Function on AWS Lambda</title>
      <dc:creator>Atul Vishwakarma</dc:creator>
      <pubDate>Fri, 24 Jul 2026 02:30:00 +0000</pubDate>
      <link>https://dev.to/vatul16/deploying-your-first-serverless-function-on-aws-lambda-15m7</link>
      <guid>https://dev.to/vatul16/deploying-your-first-serverless-function-on-aws-lambda-15m7</guid>
      <description>&lt;p&gt;For a long time, deploying an application meant provisioning a server, installing an operating system, configuring a runtime environment, and paying for that server to run 24/7—even if nobody was visiting your site.&lt;/p&gt;

&lt;p&gt;Then came &lt;strong&gt;Serverless&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Serverless doesn't mean there are no servers. It simply means &lt;em&gt;you&lt;/em&gt; don't have to manage them. With AWS Lambda, you just write your code, upload it, and AWS handles the rest. It automatically scales based on traffic, and the best part? You only pay for the exact milliseconds your code is running.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(If you followed my previous tutorial on&lt;/em&gt; &lt;a href="https://atulcodes.hashnode.dev/how-to-set-up-aws-billing-alerts-budgets" rel="noopener noreferrer"&gt;&lt;em&gt;Setting Up AWS Billing Alerts&lt;/em&gt;&lt;/a&gt;&lt;em&gt;, you already know how to keep your costs under control!)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Today, we are going to build your first serverless API endpoint using AWS Lambda and API Gateway. Let's dive in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create the Lambda Function
&lt;/h2&gt;

&lt;p&gt;Log into your AWS Management Console and search for &lt;strong&gt;Lambda&lt;/strong&gt; in the top search bar.&lt;/p&gt;

&lt;p&gt;Once you are on the Lambda dashboard, click the bright orange &lt;strong&gt;Create function&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;Configure your function with the following settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Author from scratch:&lt;/strong&gt; Leave this selected.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Function name:&lt;/strong&gt; &lt;code&gt;MyFirstServerlessAPI&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Runtime:&lt;/strong&gt; Select &lt;code&gt;Node.js 24.x&lt;/code&gt; (or the latest available Node.js version).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Architecture:&lt;/strong&gt; &lt;code&gt;x86_64&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Scroll down to the bottom and click &lt;strong&gt;Create function&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fglhvlnxzx2aolo8m9o8z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fglhvlnxzx2aolo8m9o8z.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 2: Write the Serverless Code
&lt;/h2&gt;

&lt;p&gt;You will be redirected to your new function's dashboard. Scroll down to the &lt;strong&gt;Code source&lt;/strong&gt; section. You will see an integrated code editor with a file named &lt;code&gt;index.mjs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;AWS provides a basic "Hello from Lambda" template, but let's update it so it behaves like a real API endpoint that returns JSON data.&lt;/p&gt;

&lt;p&gt;Replace the default code with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// We can log the incoming request for debugging&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Incoming event: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// Create our JSON payload&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Success! You just built your first Serverless API.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;yourRole&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cloud Engineer in the making&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Return the HTTP response&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseBody&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After pasting the code, click the &lt;strong&gt;Deploy&lt;/strong&gt; button just above the code editor. If you don't click deploy, AWS won't save your changes!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F59z0qgz06p8oi5rdhvao.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F59z0qgz06p8oi5rdhvao.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 3: Add API Gateway
&lt;/h2&gt;

&lt;p&gt;Right now, your code exists in the cloud, but there is no way for the outside world to communicate with it. We need to attach a "trigger." In this case, we want an HTTP URL that triggers our code whenever someone visits it.&lt;/p&gt;

&lt;p&gt;To do this, we use &lt;strong&gt;Amazon API Gateway&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Scroll up to the top of your function dashboard and look at the &lt;strong&gt;Function overview&lt;/strong&gt; diagram. Click on the &lt;strong&gt;+ Add trigger&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;Configure the trigger with these settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Select a source:&lt;/strong&gt; Search for and select &lt;code&gt;API Gateway&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Intent:&lt;/strong&gt; Select &lt;code&gt;Create a new API&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;API type:&lt;/strong&gt; Select &lt;code&gt;HTTP API&lt;/code&gt; (this is cheaper and simpler than REST API).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security:&lt;/strong&gt; Select &lt;code&gt;Open&lt;/code&gt; (this makes the endpoint public so we can test it in our browser).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click &lt;strong&gt;Add&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Furj3qrspa25a7nayy8ab.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Furj3qrspa25a7nayy8ab.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 4: Test Your Live API Endpoint
&lt;/h2&gt;

&lt;p&gt;AWS will take a few seconds to configure the API Gateway and attach it to your Lambda function.&lt;/p&gt;

&lt;p&gt;Once it is done, you will see API Gateway listed in your Function overview. Scroll down to the &lt;strong&gt;Configuration&lt;/strong&gt; tab, and click on &lt;strong&gt;Triggers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You will see an &lt;strong&gt;API endpoint&lt;/strong&gt; URL that looks something like this: &lt;code&gt;https://2eb2yh0j28.execute-api.us-east-1.amazonaws.com/default/MyFirstServerlessAPI&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Click that link!&lt;/p&gt;

&lt;p&gt;It will open a new tab in your browser, and you should immediately see your formatted JSON response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Success! You just built your first Serverless API."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-09T14:18:40.430Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"yourRole"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cloud Engineer in the making"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F85vw5n5szzbgj5cc1n10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F85vw5n5szzbgj5cc1n10.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;Congratulations! You just deployed a scalable, highly available microservice.&lt;/p&gt;

&lt;p&gt;Think about what you &lt;em&gt;didn't&lt;/em&gt; have to do today: You didn't configure a VPC, you didn't open ports in a security group, and you didn't install Node.js on a Linux server. AWS handled the entire underlying infrastructure, allowing you to focus entirely on writing business logic.&lt;/p&gt;

&lt;p&gt;That is the true power of Serverless.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If this tutorial helped you deploy your first Lambda function, consider&lt;/em&gt; &lt;a href="https://buymeacoffee.com/vatul16" rel="noopener noreferrer"&gt;&lt;em&gt;☕ buying me a coffee&lt;/em&gt;&lt;/a&gt; &lt;em&gt;to support my work on Atul Codes!&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Set Up AWS Billing Alerts (Before You Get a $1,000 Bill)</title>
      <dc:creator>Atul Vishwakarma</dc:creator>
      <pubDate>Fri, 17 Jul 2026 02:30:00 +0000</pubDate>
      <link>https://dev.to/vatul16/how-to-set-up-aws-billing-alerts-before-you-get-a-1000-bill-gj7</link>
      <guid>https://dev.to/vatul16/how-to-set-up-aws-billing-alerts-before-you-get-a-1000-bill-gj7</guid>
      <description>&lt;p&gt;If you talk to anyone who is just starting to learn Cloud Engineering, you will quickly find out that their biggest fear isn't failing to understand VPCs or IAM roles.&lt;/p&gt;

&lt;p&gt;Their biggest fear is waking up to a $1,000 surprise bill because they left an EC2 instance running over the weekend.&lt;/p&gt;

&lt;p&gt;Last week, we talked about &lt;a href="https://dev.to/vatul16/stop-hardcoding-aws-credentials-how-to-manage-secrets-securely-3bl"&gt;how to prevent hackers from running up your bill by securing your credentials&lt;/a&gt;. Today, we are going to protect your wallet from &lt;em&gt;your own&lt;/em&gt; mistakes.&lt;/p&gt;

&lt;p&gt;I am going to show you exactly how to set up an AWS Budget that automatically emails you the second your monthly AWS spend crosses $5.00. Do this before you spin up another server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Open the AWS Billing Dashboard
&lt;/h2&gt;

&lt;p&gt;Log in to your AWS Management Console. In the top right corner, click on your account name and select &lt;strong&gt;Billing and Cost Management&lt;/strong&gt; from the dropdown menu.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F35cv2ibqf11xaf4uknqw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F35cv2ibqf11xaf4uknqw.png" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 2: Create a New Budget
&lt;/h2&gt;

&lt;p&gt;On the left-hand navigation pane, look under the "Budgets and Planning" section and click on &lt;strong&gt;Budgets&lt;/strong&gt;. Then, click the bright orange &lt;strong&gt;Create budget&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;AWS gives you two options here: a simplified template or an advanced setup. We are going to use the custom route to ensure we get exactly what we want.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Select &lt;strong&gt;Customize (advanced)&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose &lt;strong&gt;Cost budget&lt;/strong&gt; as your budget type.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click Next.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9e65rkt8qshm8a8xktwz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9e65rkt8qshm8a8xktwz.png" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 3: Set Your $5 Threshold
&lt;/h2&gt;

&lt;p&gt;Now we need to tell AWS how much we expect to spend.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Budget name:&lt;/strong&gt; Give it a clear name, like &lt;code&gt;My-5-Dollar-Limit&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Period:&lt;/strong&gt; Monthly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Budget effective dates:&lt;/strong&gt; Recurring budget.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Budgeting method:&lt;/strong&gt; Fixed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Budget amount:&lt;/strong&gt; Enter &lt;code&gt;5.00&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This tells AWS that your absolute ceiling for the month is 5 bucks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy6391xvlt7b723gesff5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy6391xvlt7b723gesff5.png" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 4: Configure the Alert (The Most Important Step)
&lt;/h2&gt;

&lt;p&gt;Creating a budget is useless if AWS doesn't actually notify you when you cross it. We need to set an alert.&lt;/p&gt;

&lt;p&gt;Scroll down and click &lt;strong&gt;Add an alert threshold&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Threshold:&lt;/strong&gt; Enter &lt;code&gt;100&lt;/code&gt;% of actual amount. (This means AWS will trigger the alert the moment your bill hits $5.00).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Notification preferences:&lt;/strong&gt; Enter your primary email address in the "Email recipients" box.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Pro Tip: You can actually set multiple alerts! I like to set one at 50% ($2.50) and one at 100% ($5.00) just to have an early warning.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flxwum3dimckxajzacpsb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flxwum3dimckxajzacpsb.png" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 5: Review and Create
&lt;/h2&gt;

&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt; through the remaining optional screens (you do not need to attach any specific Actions for this basic alert).&lt;/p&gt;

&lt;p&gt;Review your settings on the final page, and hit &lt;strong&gt;Create budget&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;That's it! You now have a financial safety net.&lt;/p&gt;

&lt;p&gt;If you ever accidentally leave a database running, or if you provision a resource that falls outside of the AWS Free Tier, you will get an email alert before the costs spiral out of control.&lt;/p&gt;

&lt;p&gt;Building hands-on projects is the only way to master AWS, and you should not let the fear of a surprise bill hold you back. Now that your account is secure, get back to building!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If this tutorial gave you some peace of mind today, you can&lt;/em&gt; &lt;a href="https://buymeacoffee.com/vatul16" rel="noopener noreferrer"&gt;&lt;em&gt;☕ buy me a coffee here&lt;/em&gt;&lt;/a&gt; &lt;em&gt;to support my work!&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Stop Hardcoding AWS Credentials: How to Manage Secrets Securely</title>
      <dc:creator>Atul Vishwakarma</dc:creator>
      <pubDate>Fri, 10 Jul 2026 02:30:00 +0000</pubDate>
      <link>https://dev.to/vatul16/stop-hardcoding-aws-credentials-how-to-manage-secrets-securely-3bl</link>
      <guid>https://dev.to/vatul16/stop-hardcoding-aws-credentials-how-to-manage-secrets-securely-3bl</guid>
      <description>&lt;p&gt;It is a cloud engineer's worst nightmare.&lt;/p&gt;

&lt;p&gt;You finish a long coding session, type &lt;code&gt;git push origin main&lt;/code&gt;, and go to sleep. You wake up the next morning to hundreds of automated emails from AWS and a billing dashboard showing a $5,000 charge.&lt;/p&gt;

&lt;p&gt;What happened? You accidentally pushed your AWS Access Key and Secret Key to a public GitHub repository. Within seconds, automated bots scraped your keys and spun up dozens of expensive EC2 instances across the globe to mine cryptocurrency.&lt;/p&gt;

&lt;p&gt;Today, we are going to make sure this never happens to you. Here is how to stop hardcoding your AWS credentials and manage your secrets securely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bad Way: Hardcoding in Your App
&lt;/h2&gt;

&lt;p&gt;When you are just learning how to use the AWS SDK (like boto3 for Python or the AWS SDK for Node.js), it is incredibly tempting to just paste your keys directly into your code so it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ NEVER DO THIS!&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;accessKeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AKIAIOSFODNN7EXAMPLE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;secretAccessKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this code gets pushed to GitHub, your AWS account is completely compromised. We need to separate our &lt;em&gt;code&lt;/em&gt; from our &lt;em&gt;configuration&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Good Way (Local Development): AWS CLI Profiles
&lt;/h2&gt;

&lt;p&gt;Instead of putting credentials in your code, you should store them securely on your local machine using the AWS Command Line Interface (CLI).&lt;/p&gt;

&lt;p&gt;If you have the AWS CLI installed, open your terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws configure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will prompt you to enter your Access Key, Secret Key, default region (e.g., &lt;code&gt;us-east-1&lt;/code&gt;), and output format.&lt;/p&gt;

&lt;p&gt;Once you do this, AWS creates a hidden file on your machine. If you are developing on a Linux system (like Fedora) or macOS, this file lives at &lt;code&gt;~/.aws/credentials&lt;/code&gt;. On Windows, it is at &lt;code&gt;C:\Users\YOUR_USERNAME\.aws\credentials&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmt12v85u4g06mt2sbd23.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmt12v85u4g06mt2sbd23.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, when you run your code, the AWS SDK will automatically look for this hidden file and use the credentials inside it. Your code becomes completely clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ THE SAFE WAY&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// The SDK automatically pulls from your ~/.aws/credentials file!&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Good Way (Pipelines &amp;amp; CI/CD): Environment Variables
&lt;/h2&gt;

&lt;p&gt;If you are running your code inside a Docker container or a GitHub Actions pipeline, you can't manually run &lt;code&gt;aws configure&lt;/code&gt;. Instead, you use &lt;strong&gt;Environment Variables&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;AWS SDKs are programmed to automatically look for these specific variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;AWS_DEFAULT_REGION&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are working locally with a &lt;code&gt;.env&lt;/code&gt; file, it should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
AWS_DEFAULT_REGION=us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxvm4edlhqm2hrs51lkzk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxvm4edlhqm2hrs51lkzk.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Crucial Step:&lt;/strong&gt; You must add &lt;code&gt;.env&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt; file. If you track your &lt;code&gt;.env&lt;/code&gt; file in Git, you are right back to the nightmare scenario we talked about at the beginning!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ultimate Fix (Production): AWS Secrets Manager
&lt;/h2&gt;

&lt;p&gt;The methods above are perfect for personal projects, local development, and simple CI/CD pipelines.&lt;/p&gt;

&lt;p&gt;However, when you are dealing with production microservices at scale, managing dozens of &lt;code&gt;.env&lt;/code&gt; files becomes a massive security liability. In a true production environment, the best practice is to migrate credential management to &lt;strong&gt;AWS Secrets Manager&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By using Secrets Manager, you eliminate hardcoded secrets entirely, automate key rotation, and establish SOC2-aligned auditability so you know exactly which microservice accessed which secret and when.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;Security in the cloud isn't just an afterthought; it is step zero. By utilizing &lt;code&gt;aws configure&lt;/code&gt; locally and environment variables in your pipelines, you can push your code to GitHub with complete peace of mind.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(If you are using these secured credentials to write infrastructure code, be sure to bookmark my&lt;/em&gt; &lt;a href="https://atulcodes.hashnode.dev/ultimate-terraform-cheatsheet-devops" rel="noopener noreferrer"&gt;&lt;em&gt;Ultimate Terraform Cheatsheet&lt;/em&gt;&lt;/a&gt; &lt;em&gt;to speed up your workflow!)&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If this security tip helped you out today, you can&lt;/em&gt; &lt;a href="https://buymeacoffee.com/vatul16" rel="noopener noreferrer"&gt;&lt;em&gt;☕ buy me a coffee here&lt;/em&gt;&lt;/a&gt; &lt;em&gt;to support my work!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>security</category>
      <category>github</category>
    </item>
    <item>
      <title>Infrastructure as Code 101: Provisioning an AWS EC2 Instance with Terraform</title>
      <dc:creator>Atul Vishwakarma</dc:creator>
      <pubDate>Thu, 02 Jul 2026 12:30:00 +0000</pubDate>
      <link>https://dev.to/vatul16/infrastructure-as-code-101-provisioning-an-aws-ec2-instance-with-terraform-44d8</link>
      <guid>https://dev.to/vatul16/infrastructure-as-code-101-provisioning-an-aws-ec2-instance-with-terraform-44d8</guid>
      <description>&lt;p&gt;If you are currently managing your AWS infrastructure by logging into the console, clicking through menus, checking boxes, and hitting "Create," you are practicing what DevOps engineers jokingly call "&lt;strong&gt;ClickOps.&lt;/strong&gt;"&lt;/p&gt;

&lt;p&gt;While clicking around is fine for learning, it is a nightmare for production. Manual clicks are impossible to track, prone to human error, and incredibly slow to replicate. If your server goes down, you have to remember exactly which boxes you checked six months ago to rebuild it.&lt;/p&gt;

&lt;p&gt;The solution is &lt;strong&gt;Infrastructure as Code (IaC)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Today, we are diving into &lt;strong&gt;Terraform&lt;/strong&gt;, the industry standard for IaC. I am going to show you how to stop clicking and start coding by provisioning a basic Linux AWS EC2 instance using just a few lines of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Terraform?
&lt;/h2&gt;

&lt;p&gt;Created by HashiCorp, Terraform is a tool that allows you to define both cloud and on-premise resources in human-readable configuration files. It uses a declarative language called HCL (HashiCorp Configuration Language).&lt;/p&gt;

&lt;p&gt;"Declarative" means you just tell Terraform what you want (e.g., "I want one t2.micro EC2 instance"), and Terraform figures out how to communicate with AWS APIs to make it happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we write our code, you need three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An &lt;strong&gt;AWS Account&lt;/strong&gt; (the Free Tier is perfect).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;AWS CLI&lt;/strong&gt; installed and configured on your machine with your IAM credentials (so Terraform has permission to talk to your account).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terraform&lt;/strong&gt; installed on your local machine.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Step 1: Define the Provider
&lt;/h2&gt;

&lt;p&gt;Terraform can manage infrastructure on AWS, Azure, Google Cloud, and even GitHub! To tell Terraform we want to talk specifically to AWS, we need to configure a "Provider."&lt;/p&gt;

&lt;p&gt;Create a new folder on your computer, open it in your terminal, and create a file called &lt;code&gt;main.tf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Add this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&amp;gt; 5.0"
    }
  }
}

# Configure the AWS Provider
provider "aws" {
  region = "us-east-1" # Change this to your preferred region
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Write the EC2 Resource Block
&lt;/h2&gt;

&lt;p&gt;Now, we tell Terraform exactly what resource we want to build. Below your provider block in &lt;code&gt;main.tf&lt;/code&gt;, add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_instance" "my_first_server" {
  ami           = "ami-0c7217cdde317cfec" # Ubuntu 22.04 LTS in us-east-1
  instance_type = "t2.micro"

  tags = {
    Name = "AtulCodes-Terraform-Server"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Breaking down the code:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;resource&lt;/code&gt;: Tells Terraform we are creating a new piece of infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"aws_instance"&lt;/code&gt;: The specific type of resource (an EC2 instance).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"my_first_server"&lt;/code&gt;: This is just a local name we use to reference this server within our Terraform code. AWS never sees this name.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ami&lt;/code&gt;: The Amazon Machine Image ID. Note: AMIs are region-specific! The one above is for Ubuntu in us-east-1. If you are in ap-south-1 (Mumbai), you will need to grab a different AMI ID from the AWS console.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;instance_type&lt;/code&gt;: We are using t2.micro because it qualifies for the AWS Free Tier.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tags&lt;/code&gt;: This is how we actually name the server inside the AWS Console.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk0y258miadkwgj3lko50.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk0y258miadkwgj3lko50.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 3: The Core Terraform Workflow
&lt;/h2&gt;

&lt;p&gt;Terraform relies on three magical commands to get your infrastructure into the cloud: &lt;code&gt;init&lt;/code&gt;, &lt;code&gt;plan&lt;/code&gt;, and &lt;code&gt;apply&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Initialize the Directory
&lt;/h3&gt;

&lt;p&gt;Open your terminal in your project folder and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command looks at your &lt;code&gt;main.tf&lt;/code&gt; file, sees you want to use AWS, and downloads the necessary AWS plugins in the background.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Plan the Deployment
&lt;/h3&gt;

&lt;p&gt;Next, we want to see what Terraform is going to do before it actually does it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will get a large output showing exactly what resources will be created. Look for the green &lt;code&gt;+&lt;/code&gt; signs. At the bottom, it should say: &lt;code&gt;Plan: 1 to add, 0 to change, 0 to destroy&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F03i7ws7w694kwebihpdw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F03i7ws7w694kwebihpdw.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. Apply the Code
&lt;/h3&gt;

&lt;p&gt;If the plan looks good, it is time to deploy!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform will show you the plan one more time and ask you to type &lt;code&gt;yes&lt;/code&gt; to confirm. Type &lt;code&gt;yes&lt;/code&gt; and hit enter. Within a few seconds, Terraform will communicate with AWS and spin up your server!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Verify in the AWS Console
&lt;/h2&gt;

&lt;p&gt;Don't just take my word for it. Log into your AWS Management Console, navigate to the &lt;strong&gt;EC2 Dashboard&lt;/strong&gt;, and click on &lt;strong&gt;Instances&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You will see a brand-new server named &lt;code&gt;AtulCodes-Terraform-Server&lt;/code&gt; sitting right there, in a "Running" state. You provisioned that without clicking a single button in the UI!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbv194ril5bvh09ioev6r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbv194ril5bvh09ioev6r.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 5: Destroy the Evidence (Crucial!)
&lt;/h2&gt;

&lt;p&gt;One of the best features of IaC is how easy it is to clean up. If you leave this server running, AWS might eventually charge you for it.&lt;/p&gt;

&lt;p&gt;To tear down everything we just built, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;yes&lt;/code&gt; when prompted. Terraform will gracefully delete the EC2 instance it created.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;You have officially taken your first step into Infrastructure as Code. By writing your infrastructure as code, you can version control it in Git, review it in Pull Requests, and ensure your staging environments are exact replicas of your production environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next week, I am dropping my Ultimate Terraform Cheatsheet!&lt;/strong&gt; It will cover all the essential commands, state management, and variable syntax you need for your daily DevOps workflows. Make sure you hit the subscribe button so you don't miss it.&lt;/p&gt;




&lt;p&gt;If this tutorial helped you deploy your first Terraform resource, you can &lt;a href="https://buymeacoffee.com/vatul16" rel="noopener noreferrer"&gt;☕ buy me a coffee here&lt;/a&gt; to support my work!&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>devops</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>TIL: How to Fix the "Port Already in Use" Error in Docker</title>
      <dc:creator>Atul Vishwakarma</dc:creator>
      <pubDate>Tue, 30 Jun 2026 03:30:00 +0000</pubDate>
      <link>https://dev.to/vatul16/til-how-to-fix-the-port-already-in-use-error-in-docker-1b65</link>
      <guid>https://dev.to/vatul16/til-how-to-fix-the-port-already-in-use-error-in-docker-1b65</guid>
      <description>&lt;p&gt;We have all been there. You just built a fresh Docker image, you confidently type your d&lt;code&gt;ocker run&lt;/code&gt; command into the terminal, and &lt;strong&gt;BAM&lt;/strong&gt;. You are hit with a massive wall of red text that looks something like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Error starting userland proxy: listen tcp4 0.0.0.0:3000: bind: address already in use.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This error (often referred to as &lt;code&gt;EADDRINUSE&lt;/code&gt;) simply means that something else on your computer is already using the port you are trying to assign to your Docker container.&lt;/p&gt;

&lt;p&gt;Today I Learned (TIL) the fastest way to find that rogue process and terminate it so you can get back to coding. Here is the two-step fix for Mac and Linux users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Find the Rogue Process
&lt;/h2&gt;

&lt;p&gt;To find out exactly which application is hoarding your port, we will use the &lt;code&gt;lsof&lt;/code&gt; (List Open Files) command.&lt;/p&gt;

&lt;p&gt;Open your terminal and type this command, replacing &lt;code&gt;3000&lt;/code&gt; with whatever port is giving you trouble:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lsof &lt;span class="nt"&gt;-i&lt;/span&gt; :3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will return a list of processes using that port. Look under the &lt;strong&gt;PID&lt;/strong&gt; (Process ID) column. You need that number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Kill the Process
&lt;/h2&gt;

&lt;p&gt;Now that we have the Process ID, we can force it to shut down using the &lt;code&gt;kill&lt;/code&gt; command. Pass the &lt;code&gt;-9&lt;/code&gt; flag to ensure it terminates immediately, followed by the PID you found in Step 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-9&lt;/span&gt; 14235
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it! Your port is now free. You can run your &lt;code&gt;docker run -p 3000:3000 my-app&lt;/code&gt; command again, and it will spin up perfectly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bonus: What if it's a "Ghost" Docker Container?
&lt;/h2&gt;

&lt;p&gt;Sometimes, the port is being blocked because you started a Docker container earlier, forgot about it, and left it running in the background.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;lsof&lt;/code&gt; doesn't solve your problem, quickly check your active Docker containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find the container using your port, grab its Container ID, and stop it gracefully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker stop &amp;lt;CONTAINER_ID&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;📚 Catch up on this Docker Series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: &lt;a href="https://dev.to/vatul16/how-to-dockerize-a-nodejs-app-in-5-easy-steps-44d4"&gt;How to Dockerize a Node.js App in 5 Easy Steps&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 2: &lt;a href="https://dev.to/vatul16/automate-your-docker-builds-with-github-actions-4nbp"&gt;Automate Your Docker Builds with GitHub Actions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this quick tip saved you some debugging time today, you can &lt;a href="https://buymeacoffee.com/vatul16" rel="noopener noreferrer"&gt;☕ buy me a coffee here&lt;/a&gt; to support my work!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>linux</category>
      <category>devops</category>
      <category>todayilearned</category>
    </item>
    <item>
      <title>TerraGuard AI: Adding an AI Brain to Terraform Drift Detection — Design Decisions, Trade-offs, and Lessons Learned</title>
      <dc:creator>Atul Vishwakarma</dc:creator>
      <pubDate>Mon, 29 Jun 2026 05:30:00 +0000</pubDate>
      <link>https://dev.to/vatul16/terraguard-ai-adding-an-ai-brain-to-terraform-drift-detection-design-decisions-trade-offs-and-7dg</link>
      <guid>https://dev.to/vatul16/terraguard-ai-adding-an-ai-brain-to-terraform-drift-detection-design-decisions-trade-offs-and-7dg</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/vatul16/TerraGuard-AI" rel="noopener noreferrer"&gt;https://github.com/vatul16/TerraGuard-AI&lt;/a&gt; — full source, Terraform infra, drift detection engine, and CI/CD workflows.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When I finished &lt;a href="https://github.com/vatul16/terratier" rel="noopener noreferrer"&gt;TerraTier&lt;/a&gt;, the 3-tier AWS architecture project I wrote about last time, I wanted to build something that pushed in a different direction. TerraTier was about the infrastructure layer — networking, security boundaries, secrets management. This project is about the operational layer: what happens &lt;em&gt;after&lt;/em&gt; the infrastructure is running, when someone inevitably makes a change outside of Terraform.&lt;/p&gt;

&lt;p&gt;The result is &lt;strong&gt;TerraGuard AI&lt;/strong&gt; — an event-driven drift detection system that catches manual AWS console changes, classifies their risk using an LLM, and routes context-rich alerts intelligently instead of just firing a generic notification every time a description field changes. A Node.js API on ECS Fargate, a PostgreSQL database on RDS, and an ALB are the infrastructure being monitored. GitHub Actions, Python, and Groq's API for Llama 3.3 are how the detection and analysis work.&lt;/p&gt;

&lt;p&gt;This article is about the decisions behind why it works the way it does, and what I'd change.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem with standard drift detection
&lt;/h2&gt;

&lt;p&gt;The textbook implementation of Terraform drift detection is: run &lt;code&gt;terraform plan -detailed-exitcode&lt;/code&gt; on a cron job, check the exit code, send an alert if it's non-zero. This works, and it takes maybe 30 lines of shell script to implement. The problem isn't the detection — it's what happens to the humans on the other end of the alert.&lt;/p&gt;

&lt;p&gt;In any environment where more than one person touches the AWS console, drift is nearly constant. Someone tags a security group directly in the console, or updates a description, or changes a retention period on a log group that wasn't in Terraform yet. Each of these fires the exact same alert as "port 22 opened to 0.0.0.0/0 on a production security group." After the third or fourth time the on-call engineer wakes up to an alert and finds a tag change, they start doing what humans do with noisy signals: they ignore them.&lt;/p&gt;

&lt;p&gt;Alert fatigue is a well-documented problem in SRE literature, but most drift detection tooling solves it by giving you more configuration options — exception lists, ignore patterns, threshold tuning. That's fighting symptoms. The actual cause is that the alerting system has no concept of severity. It knows something changed; it doesn't know what that means.&lt;/p&gt;

&lt;p&gt;The insight that shaped this project is that detection and classification are two separate problems, and they need different tools. Terraform is genuinely excellent at detection — it has the full resource schema, it talks directly to the AWS APIs, and its plan output is deterministic and reliable. It has no idea what a change means from a security or business perspective. That's a reasoning problem, and it's exactly what language models are good at.&lt;/p&gt;

&lt;p&gt;So TerraGuard AI uses Terraform to detect drift, and an LLM to classify it. The two tools do the parts they're each actually suited for.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decision 1: AI for classification, not detection
&lt;/h2&gt;

&lt;p&gt;This distinction is worth dwelling on, because it's easy to imagine AI being used to &lt;em&gt;find&lt;/em&gt; drift — some vector-similarity approach comparing current state to desired state, or something. That would be using AI to badly replicate something Terraform already does perfectly.&lt;/p&gt;

&lt;p&gt;The LLM layer in this project only ever sees the parsed output of &lt;code&gt;terraform plan&lt;/code&gt;. Drift is already confirmed by the time the AI gets involved. What the AI does is answer a different set of questions: Is this change a security risk or a configuration nuance? Should it be reverted or adopted into the Terraform codebase? What is the exact command someone should run to remediate it, right now, without having to look anything up?&lt;/p&gt;

&lt;p&gt;The prompt asks Groq's Llama 3.3 to act as a Senior Cloud Security Engineer and return a structured JSON response with a risk level (&lt;code&gt;CRITICAL&lt;/code&gt; / &lt;code&gt;HIGH&lt;/code&gt; / &lt;code&gt;MEDIUM&lt;/code&gt; / &lt;code&gt;LOW&lt;/code&gt; / &lt;code&gt;INFO&lt;/code&gt;), a score out of 10, an impact summary, a recommended action (&lt;code&gt;REVERT&lt;/code&gt; / &lt;code&gt;ADOPT&lt;/code&gt; / &lt;code&gt;INVESTIGATE&lt;/code&gt; / &lt;code&gt;MONITOR&lt;/code&gt;), and a concrete remediation command. The temperature is set to 0.1 — not 0, because zero-temperature responses can be oddly mechanical, but close enough to zero that the analysis is consistent and factual rather than creative.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;risk_level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CRITICAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;risk_score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Security&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Security group ingress rule added exposing SSH port 22 to all internet traffic.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;impact&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Any host on the internet can attempt SSH brute-force or exploitation against ECS tasks.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REVERT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;remediation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;terraform apply -target=aws_security_group.ecs_tasks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Opening port 22 to 0.0.0.0/0 is a critical security misconfiguration...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Based on that response, the routing is simple: &lt;code&gt;CRITICAL&lt;/code&gt; or &lt;code&gt;HIGH&lt;/code&gt; goes to Slack immediately, plus a GitHub Issue for the audit trail. &lt;code&gt;MEDIUM&lt;/code&gt;, &lt;code&gt;LOW&lt;/code&gt;, or &lt;code&gt;INFO&lt;/code&gt; creates a GitHub Issue that goes into the backlog — no Slack notification, no on-call page.&lt;/p&gt;

&lt;p&gt;The practical effect is that the Slack channel only gets messages that actually need a human to look at something now. The rest creates a self-organizing backlog of minor drift that can be handled during normal working hours. That's not a feature — it's the entire point.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decision 2: Groq over AWS Bedrock
&lt;/h2&gt;

&lt;p&gt;The obvious choice for an AWS-native project would be Amazon Bedrock. The LLM call would live inside the VPC, authentication would use the same IAM role the rest of the infrastructure uses, and there would be no external API dependency.&lt;/p&gt;

&lt;p&gt;I chose Groq instead, for two reasons that turned out to matter more than I expected.&lt;/p&gt;

&lt;p&gt;The first is speed. Groq's custom LPU hardware runs Llama 3.3 70B at a genuinely unusual pace — we're talking single-digit seconds for a complete risk analysis. Bedrock inference at a comparable model size is slower, and when drift detection runs in a GitHub Actions workflow with a 6-hour cron cadence, latency matters less than it would in a real-time system. But for local development and testing, where you're running the detector manually and waiting for output, the speed difference makes iteration significantly less frustrating.&lt;/p&gt;

&lt;p&gt;The second reason is cost. Groq's free tier is 14,400 requests per day, which is more than enough for a drift detection workflow that runs every 6 hours and only calls the API when drift is actually found. Bedrock charges per-token with no free tier. For a personal portfolio project that also needs to pay for ECS, RDS, NAT Gateway, and an ALB, not paying for inference is a meaningful constraint.&lt;/p&gt;

&lt;p&gt;The trade-off is the external dependency. The drift detector now requires an outbound HTTPS connection from the GitHub Actions runner to &lt;code&gt;api.groq.com&lt;/code&gt;, and if Groq is down or rate-limiting, the analysis step fails. I handled this with a fallback in the Python script: if the Groq call fails or returns unparseable JSON, the detector falls back to a &lt;code&gt;MEDIUM&lt;/code&gt; risk assessment with a manual review recommendation rather than crashing entirely. The drift is still logged and acted on; it just lacks the AI analysis.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decision 3: GitHub Actions over Lambda
&lt;/h2&gt;

&lt;p&gt;The architecturally "purer" approach to scheduled drift detection is a Lambda function triggered by EventBridge on a cron schedule, with an additional EventBridge rule listening to CloudTrail events for real-time detection when a console change happens. That's closer to the enterprise architecture in the project's original design.&lt;/p&gt;

&lt;p&gt;I built it on GitHub Actions instead, and I think that was the right call for this stage.&lt;/p&gt;

&lt;p&gt;The practical argument: GitHub Actions is free, already configured for the project, and doesn't require provisioning, IAM-scoping, or maintaining a separate compute resource. A Lambda function adds at least three more Terraform resources to manage, an ECR image or deployment package to maintain, and CloudWatch Logs to check when something goes wrong — all of which is real work that doesn't directly demonstrate anything new. The drift detection workflow in GitHub Actions does the same job with less infrastructure surface area and a simpler debugging story (&lt;code&gt;Actions&lt;/code&gt; tab in GitHub, logs right there).&lt;/p&gt;

&lt;p&gt;The conceptual argument: the drift detector is not a latency-sensitive workload. A 6-hour detection window is fine for a portfolio project, and even for many real production environments, catching a security group change within 6 hours is a meaningful improvement over catching it when someone manually reviews the AWS console. Real-time detection via CloudTrail + EventBridge is in the project roadmap, but it's a layer added on top of a working baseline, not a prerequisite.&lt;/p&gt;

&lt;p&gt;Where this decision has a genuine cost: the GitHub Actions runner needs AWS credentials. That means &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt; as repository secrets — long-lived credentials stored in GitHub, which is a less secure pattern than OIDC role assumption. The Terraform IAM code in the project already scaffolds the OIDC setup (&lt;code&gt;aws_iam_openid_connect_provider&lt;/code&gt;, &lt;code&gt;aws_iam_role&lt;/code&gt; with a GitHub OIDC trust policy), and migrating to keyless authentication is the highest-priority item on the roadmap. I built with access keys first to get the workflow working without debugging OIDC trust policies at the same time, which is a trade-off I'd make again in the same situation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decision 4: Minimal infrastructure being monitored
&lt;/h2&gt;

&lt;p&gt;A reasonable design question for a drift detector is: what infrastructure should it monitor? You could make the case that the more complex the infrastructure, the more impressive the demo — VPCs with 20 resources, multiple environments, Auto Scaling Groups, the works.&lt;/p&gt;

&lt;p&gt;I went the opposite direction. The monitored infrastructure is as simple as it can be while still being realistic: one ECS Fargate service, one RDS PostgreSQL database, one ALB. That's a pattern that appears in real production environments constantly — it's the architecture for a backend API behind a load balancer with persistent storage. Nothing exotic.&lt;/p&gt;

&lt;p&gt;The reason for keeping it simple is that the interesting thing in this project is the drift detection and classification layer, not the infrastructure. A 40-resource VPC with multiple tiers and Auto Scaling Groups would take most of the explanation budget to describe, and would make the CI/CD and drift detection workflows harder to follow for someone reading the code. By keeping the infrastructure simple and well-understood, someone reading the repository can quickly get past "what does this infrastructure do" and get to "how does the drift detection actually work" — which is the part worth explaining.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decision 5: Separating infrastructure and application deployment
&lt;/h2&gt;

&lt;p&gt;The most practically important single line in the entire &lt;code&gt;ecs.tf&lt;/code&gt; file is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ignore_changes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;task_definition&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this, &lt;code&gt;terraform apply&lt;/code&gt; and the &lt;code&gt;deploy-app.yml&lt;/code&gt; GitHub Actions workflow would fight each other. The deploy workflow updates the ECS task definition to use a new ECR image URI (tagged with the git SHA) every time new code is pushed to &lt;code&gt;app/**&lt;/code&gt;. If Terraform's ECS service resource didn't ignore task definition changes, the next &lt;code&gt;terraform apply&lt;/code&gt; — triggered by the drift detector or by a manual infra change — would overwrite the CI/CD-deployed image with whatever image URI is in the Terraform variables. Deploying a new version of the app would immediately cause "drift" against the Terraform-managed state.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ignore_changes&lt;/code&gt; lifecycle setting tells Terraform: "I know this attribute might differ from what's in the config, and that's intentional — CI/CD owns it, not you." This is the standard pattern for separating infrastructure management (Terraform's job) from application deployment (CI/CD's job) on ECS, and it's one of those things that sounds obvious in retrospect but takes a debugging session to discover if you haven't seen it before.&lt;/p&gt;

&lt;p&gt;The deploy workflow itself follows a pattern worth understanding: it doesn't just push an image and call &lt;code&gt;aws ecs update-service --force-new-deployment&lt;/code&gt;. It downloads the &lt;em&gt;current&lt;/em&gt; live task definition from AWS, replaces only the container image URI, registers the modified definition as a new revision, and then updates the service to use that revision. The reason for this sequence is that the task definition contains Secrets Manager ARNs, IAM role ARNs, environment variable sets, and health check configuration that are all managed by Terraform. Overwriting the entire task definition from a CI/CD workflow would blow away those fields. Downloading the live definition and patching only the image preserves everything else.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decision 6: Secrets Manager with ECS secrets references, not environment variables
&lt;/h2&gt;

&lt;p&gt;The Node.js application gets its database connection details through a combination of plain environment variables and a &lt;code&gt;secrets&lt;/code&gt; block in the ECS task definition.&lt;/p&gt;

&lt;p&gt;Non-sensitive values — &lt;code&gt;DB_HOST&lt;/code&gt;, &lt;code&gt;DB_PORT&lt;/code&gt;, &lt;code&gt;DB_NAME&lt;/code&gt;, &lt;code&gt;DB_USER&lt;/code&gt;, &lt;code&gt;NODE_ENV&lt;/code&gt;, &lt;code&gt;PORT&lt;/code&gt; — are passed as plain environment variables. They're not sensitive: knowing the RDS hostname doesn't help an attacker get into the database.&lt;/p&gt;

&lt;p&gt;The password is different. The ECS &lt;code&gt;secrets&lt;/code&gt; block references the Secrets Manager secret by ARN, and at container startup, the ECS agent fetches the value, injects it as &lt;code&gt;DB_PASSWORD&lt;/code&gt; in the container's environment, and never writes it anywhere that shows up in the task definition JSON, CloudWatch logs, or the AWS console. The only evidence it existed is the ARN reference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;secrets&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DB_PASSWORD"&lt;/span&gt;
    &lt;span class="nx"&gt;valueFrom&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_secretsmanager_secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This also required a specific IAM policy attached to the ECS &lt;em&gt;execution&lt;/em&gt; role (not the task role — the agent that starts containers, not the container itself):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;   &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"secretsmanager:GetSecretValue"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_secretsmanager_secret&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db_password&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scoping matters. The policy grants access to exactly one secret ARN, not &lt;code&gt;"Resource": "*"&lt;/code&gt;. If an attacker found a way to escalate privileges from inside the container to the execution role, they could read one specific secret with a known ARN, not any secret in the account. That's a meaningful reduction in blast radius for a one-line change.&lt;/p&gt;




&lt;h2&gt;
  
  
  A race condition I hit and what it taught me
&lt;/h2&gt;

&lt;p&gt;When I added the Node.js app port (&lt;code&gt;3000&lt;/code&gt;) to the Terraform configuration — replacing the placeholder &lt;code&gt;80&lt;/code&gt; that was there from the initial setup — running &lt;code&gt;terraform apply&lt;/code&gt; produced an error I hadn't seen before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: deleting ELBv2 Target Group: ResourceInUse: Target group is currently in use by a listener or a rule
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happened: changing the port on an ALB target group forces replacement (Terraform creates a new target group, then tries to delete the old one). But the ALB listener still referenced the old target group when Terraform tried to delete it. The listener update and the target group deletion were running in the wrong order — or more precisely, in parallel, and the deletion raced against the listener update and lost.&lt;/p&gt;

&lt;p&gt;The fix was simply running &lt;code&gt;terraform apply&lt;/code&gt; a second time. The first apply created the new target group and partially updated the listener, but the listener change hadn't completed by the time the old target group deletion ran. The second apply found the listener already pointing to the new target group and successfully deleted the old one.&lt;/p&gt;

&lt;p&gt;I mention this because it's the kind of error that's alarming-looking and turns out to be completely benign — and knowing that pattern ("Terraform apply failed with ResourceInUse, run it again") is one of those things that takes a long time to learn from documentation and about five seconds to learn from experience. If you hit it: apply again.&lt;/p&gt;

&lt;p&gt;The deeper lesson is about Terraform's dependency graph. Terraform builds an explicit dependency graph from the &lt;code&gt;depends_on&lt;/code&gt; and implicit references in your configuration, and parallelizes everything it can within that graph. When you're modifying a resource that has something depending on it &lt;em&gt;and&lt;/em&gt; you're replacing the resource rather than updating it in-place, the order of operations for the replacement can be non-obvious. Looking at the &lt;code&gt;terraform plan&lt;/code&gt; output more carefully — specifically the &lt;code&gt;-/+&lt;/code&gt; indicators for "destroy and create replacement" — before applying would have flagged this in advance.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd build next
&lt;/h2&gt;

&lt;p&gt;The most significant gap in the current design is real-time detection. Running &lt;code&gt;terraform plan&lt;/code&gt; every 6 hours means there's a window of up to 6 hours between a manual console change and the drift detection alert. For most configuration changes that's probably fine. For a security group rule opening port 22 to the internet, it's not.&lt;/p&gt;

&lt;p&gt;The architecture for closing that gap exists: an EventBridge rule that listens to CloudTrail events for specific API calls (&lt;code&gt;AuthorizeSecurityGroupIngress&lt;/code&gt;, &lt;code&gt;ModifyDBInstance&lt;/code&gt;, &lt;code&gt;AttachRolePolicy&lt;/code&gt;, and similar high-risk mutations), triggering a Lambda function that runs the plan-and-classify pipeline on demand, within seconds of the change. That's the enterprise-grade pattern that the project's original design was based on, and it's what I'd add in v2.&lt;/p&gt;

&lt;p&gt;OIDC authentication for GitHub Actions is the other immediate priority. Storing &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt; as repository secrets is functional but carries unnecessary risk — those credentials persist until they're rotated, and rotating them requires remembering they exist. The OIDC setup in &lt;code&gt;iam.tf&lt;/code&gt; is already scaffolded; it's a workflow change and a secret deletion away from being the default.&lt;/p&gt;

&lt;p&gt;A drift history store is something I've sketched but not built. Right now, every drift detection run is stateless — the result goes to Slack or GitHub Issues, and that's the end of it. If you wanted to answer "how often does our infrastructure drift, in which direction, and is it getting better or worse over time?", there's nowhere to look. A DynamoDB table storing detection results per run, with a simple dashboard, would turn TerraGuard AI from a reactive alerting tool into something with genuine observability over infrastructure compliance trends.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;The most useful framing I found for this project is that it's not an AI project with some infrastructure bolted on, and it's not an infrastructure project with some Python bolted on. It's an operations automation project that happens to use AI for the part where you'd otherwise need a human to make a judgment call.&lt;/p&gt;

&lt;p&gt;That framing matters because it determines what you actually have to understand to build it well. The Groq integration is a dozen lines of Python — the interesting engineering is in understanding when to call it, with what inputs, and what to do with the output, which requires thinking about the infrastructure, the CI/CD pipeline, the IAM model, and the alerting routing as a system. None of those are complicated individually. The interesting work is making them compose.&lt;/p&gt;

&lt;p&gt;The full source is at &lt;a href="https://github.com/vatul16/terraguard-ai" rel="noopener noreferrer"&gt;https://github.com/vatul16/terraguard-ai&lt;/a&gt; — Terraform, Python detector, GitHub Actions workflows, and the Node.js app. I'm actively looking for Cloud/DevOps Engineer roles as I transition from full-stack development, and I'd genuinely enjoy talking through any part of this with someone who has questions about a specific decision. You can find me on &lt;a href="https://linkedin.com/in/vatul16" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Previous project: &lt;a href="https://github.com/vatul16/terratier" rel="noopener noreferrer"&gt;TerraTier&lt;/a&gt; — Production-grade 3-tier AWS architecture with Terraform, Auto Scaling Groups, two ALBs, four subnet tiers, and SSM Session Manager.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>aws</category>
      <category>devops</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>Automate Your Docker Builds with GitHub Actions</title>
      <dc:creator>Atul Vishwakarma</dc:creator>
      <pubDate>Thu, 25 Jun 2026 12:30:00 +0000</pubDate>
      <link>https://dev.to/vatul16/automate-your-docker-builds-with-github-actions-4nbp</link>
      <guid>https://dev.to/vatul16/automate-your-docker-builds-with-github-actions-4nbp</guid>
      <description>&lt;p&gt;Over the last two weeks, we solved two massive DevOps problems. First, we stopped deploying manually by &lt;a href="https://dev.to/vatul16/stop-deploying-manually-how-to-build-your-first-cicd-pipeline-with-github-actions-80c"&gt;building a CI/CD pipeline with GitHub Actions&lt;/a&gt;. Then, we solved the "it works on my machine" problem by &lt;a href="https://dev.to/vatul16/how-to-dockerize-a-nodejs-app-in-5-easy-steps-44d4"&gt;containerizing a Node.js app with Docker&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Today, we are going to combine these two superpowers.&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;docker build&lt;/code&gt; manually on your laptop every time you change a line of code is tedious. Instead, we are going to configure GitHub Actions to automatically build your Docker image and push it to Docker Hub the second you push your code to the &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;p&gt;Let's automate it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A free &lt;a href="https://hub.docker.com/" rel="noopener noreferrer"&gt;Docker Hub&lt;/a&gt; account.&lt;/li&gt;
&lt;li&gt;A basic Node.js application with a &lt;code&gt;Dockerfile&lt;/code&gt; (If you don't have one, follow my &lt;a href="https://dev.to/vatul16/how-to-dockerize-a-nodejs-app-in-5-easy-steps-44d4"&gt;5-step Docker tutorial here&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;A GitHub repository with an active Actions workflow (Learn &lt;a href="https://dev.to/vatul16/stop-deploying-manually-how-to-build-your-first-cicd-pipeline-with-github-actions-80c"&gt;how to set one up in 10 minutes here&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Create a Docker Hub Access Token
&lt;/h2&gt;

&lt;p&gt;To let GitHub securely push images to your Docker Hub account, we need to give it a "password" in the form of an Access Token.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log in to Docker Hub.&lt;/li&gt;
&lt;li&gt;Click your profile picture in the top right and select &lt;strong&gt;Account Settings&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Personal access tokens&lt;/strong&gt; then &lt;strong&gt;Generate new token&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Name it &lt;code&gt;github-actions-token&lt;/code&gt;, give it "Read &amp;amp; Write" permissions, and click Generate.&lt;/li&gt;
&lt;li&gt;Copy this token immediately (you won’t be able to see it again).&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgrmwoq2soyeax4md75i2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgrmwoq2soyeax4md75i2.png" alt="Docker PAT Generation Image" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 2: Add Your Secrets to GitHub
&lt;/h2&gt;

&lt;p&gt;Now, we need to hand that token over to GitHub safely. Never hardcode passwords in your code!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to your repository on GitHub.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Settings&lt;/strong&gt; tab at the top.&lt;/li&gt;
&lt;li&gt;On the left sidebar, scroll down to &lt;strong&gt;Secrets and variables&lt;/strong&gt; and click &lt;strong&gt;Actions&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click the green &lt;strong&gt;New repository secret&lt;/strong&gt; button.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Create two distinct secrets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name: &lt;code&gt;DOCKER_USERNAME&lt;/code&gt; | Secret: Your actual Docker Hub username.&lt;/li&gt;
&lt;li&gt;Name: &lt;code&gt;DOCKER_PASSWORD&lt;/code&gt; | Secret: The Access Token you copied in Step 1.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3d7s22moqu1oa2nu9lzi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3d7s22moqu1oa2nu9lzi.png" alt="Github Secrets" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 3: Update Your GitHub Actions Workflow
&lt;/h2&gt;

&lt;p&gt;Open up the &lt;code&gt;.github/workflows/ci.yml&lt;/code&gt; file we created a couple of weeks ago. We are going to add a new job to our pipeline.&lt;/p&gt;

&lt;p&gt;Replace your existing file contents with this updated YAML code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Node.js CI/CD and Docker Build

on:
  push:
    branches: [ "main" ]

jobs:
  build-and-push-docker-image:
    runs-on: ubuntu-latest

    steps:
    # Step A: Check out the repository code
    - name: Checkout code
      uses: actions/checkout@v3

    # Step B: Log in to Docker Hub using our secure secrets
    - name: Log in to Docker Hub
      uses: docker/login-action@v2
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}

    # Step C: Build the Docker image and push it to Docker Hub
    - name: Build and push
      uses: docker/build-push-action@v4
      with:
        context: .
        push: true
        # Replace 'yourusername' with your actual Docker Hub username
        tags: yourusername/my-node-app:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Make sure to change &lt;code&gt;yourusername&lt;/code&gt; on the very last line to your actual Docker Hub username!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Push and Watch the Automation
&lt;/h2&gt;

&lt;p&gt;Save your file, commit the changes, and push it up to GitHub.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git add .github/workflows/ci.yml
git commit -m "feat: add docker build and push automation"
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Head over to the &lt;strong&gt;Actions&lt;/strong&gt; tab in your GitHub repository. You will see your new workflow spinning up. GitHub is now renting a tiny Ubuntu server, checking out your code, logging into Docker Hub, building your image based on your &lt;code&gt;Dockerfile&lt;/code&gt;, and pushing the final product to the cloud.&lt;/p&gt;

&lt;p&gt;Once you get the green checkmark, go check your Docker Hub profile. Your brand-new image will be sitting right there, ready to be pulled by any server in the world.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fex20793zpaquza0p8ef5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fex20793zpaquza0p8ef5.png" alt="GitHub Actions Result" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;You have just built a professional-grade continuous delivery pipeline.&lt;/p&gt;

&lt;p&gt;From now on, all you have to do is write your code and type &lt;code&gt;git push&lt;/code&gt;. The robots handle the rest. This is the exact workflow used by top tech companies, and you just implemented it for free.&lt;/p&gt;

&lt;p&gt;Next week, we are going to look at a quick debugging trick: how to fix the dreaded "Port Already in Use" error when working with Docker locally.&lt;/p&gt;




&lt;p&gt;If you are enjoying this DevOps series, you can &lt;a href="https://buymeacoffee.com/vatul16" rel="noopener noreferrer"&gt;☕ buy me a coffee here&lt;/a&gt; to support my work.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>githubactions</category>
      <category>cicd</category>
    </item>
    <item>
      <title>How to Dockerize a Node.js App in 5 Easy Steps</title>
      <dc:creator>Atul Vishwakarma</dc:creator>
      <pubDate>Tue, 23 Jun 2026 12:30:00 +0000</pubDate>
      <link>https://dev.to/vatul16/how-to-dockerize-a-nodejs-app-in-5-easy-steps-44d4</link>
      <guid>https://dev.to/vatul16/how-to-dockerize-a-nodejs-app-in-5-easy-steps-44d4</guid>
      <description>&lt;p&gt;"But it works on my machine!" If you are a developer, you have likely said this exact phrase. Your code runs perfectly on your laptop, but the moment you hand it to a coworker or try to deploy it to a production server, it crashes. The Node version is wrong, a dependency is missing, or the environment variables are mismatched.&lt;/p&gt;

&lt;p&gt;This is exactly the problem &lt;strong&gt;Docker&lt;/strong&gt; solves.&lt;/p&gt;

&lt;p&gt;Docker allows you to package your application and all of its dependencies into a single, standardized unit. Today, I am going to show you how to containerize a simple Node.js application in 5 easy steps so it runs perfectly anywhere, every single time.&lt;/p&gt;

&lt;p&gt;Before we write the code, let's clear up the biggest point of confusion for beginners.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Concept: Image vs. Container
&lt;/h2&gt;

&lt;p&gt;People often use these terms interchangeably, but they are completely different things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Docker Image: This is the recipe. It is a static, read-only file that contains your source code, libraries, dependencies, and tools. An image does not "run."&lt;/li&gt;
&lt;li&gt;The Docker Container: This is the cake. It is the running, active instance of your Docker Image. You can start, stop, and delete a container.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You build an Image once, and you can spin up a hundred Containers from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;&lt;strong&gt;Docker Desktop&lt;/strong&gt;&lt;/a&gt; installed on your machine.&lt;/li&gt;
&lt;li&gt;A basic understanding of the terminal.&lt;/li&gt;
&lt;li&gt;Node.js installed locally (just for our initial setup).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Create a Simple Node.js App
&lt;/h2&gt;

&lt;p&gt;We need something to containerize. Let's build a dead-simple Express server. Open your terminal, create a new folder, and initialize a project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir my-docker-app
cd my-docker-app
npm init -y
npm install express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create a file named server.js and paste in this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require('express');
const app = express();
const PORT = 3000;

app.get('/', (req, res) =&amp;gt; {
  res.send('Hello from inside a Docker Container! 🐳');
});

app.listen(PORT, () =&amp;gt; {
  console.log(`Server is running on port ${PORT}`);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, open your package.json file and add a start script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"scripts": {
  "start": "node server.js"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbb1hfh6evwbibhm4g2fu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbb1hfh6evwbibhm4g2fu.png" alt=" " width="315" height="207"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 2: Write the Dockerfile
&lt;/h2&gt;

&lt;p&gt;The Dockerfile is a text document that contains all the commands needed to assemble your Image. In the root of your project folder, create a new file named exactly Dockerfile (no file extension).&lt;/p&gt;

&lt;p&gt;Add the following instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 1. Specify the base image
FROM node:18-alpine

# 2. Set the working directory inside the container
WORKDIR /usr/src/app

# 3. Copy package.json and package-lock.json first
COPY package*.json ./

# 4. Install dependencies
RUN npm install

# 5. Copy the rest of your application code
COPY . .

# 6. Expose the port your app runs on
EXPOSE 3000

# 7. Define the command to run your app
CMD ["npm", "start"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why do we copy the &lt;code&gt;package.json&lt;/code&gt; file before the rest of the code?&lt;/strong&gt; Docker builds images in layers and caches them. If you change a single line in &lt;code&gt;server.js&lt;/code&gt;, Docker doesn't need to re-install all your npm packages. It uses the cached layer for the dependencies, making your builds incredibly fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Add a .dockerignore File
&lt;/h2&gt;

&lt;p&gt;You wouldn't push your &lt;code&gt;node_modules&lt;/code&gt; folder to GitHub, and you shouldn't copy it into your Docker image either. Your container should install its own clean dependencies.&lt;/p&gt;

&lt;p&gt;Create a file named &lt;code&gt;.dockerignore&lt;/code&gt; in your root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules
npm-debug.log
.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fegwnmnjuvkrc9ry15ek9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fegwnmnjuvkrc9ry15ek9.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 4: Build the Docker Image
&lt;/h2&gt;

&lt;p&gt;Now it is time to bake the recipe. Open your terminal in the root of your project and run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t my-node-app .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;-t&lt;/code&gt; flag "tags" your image with a readable name (&lt;code&gt;my-node-app&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;.&lt;/code&gt; at the very end tells Docker to look for the Dockerfile in the current directory. Do not forget the period!&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd4dbu1v5l0jfbgfof9fh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd4dbu1v5l0jfbgfof9fh.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 5: Run the Docker Container
&lt;/h2&gt;

&lt;p&gt;Your image is built. Now, let's bring it to life by spinning up a container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -p 3000:3000 -d my-node-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;-p 3000:3000&lt;/code&gt; flag maps port 3000 on your physical laptop to port 3000 inside the isolated container.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;-d&lt;/code&gt; flag runs the container in "detached" mode so it runs in the background, keeping your terminal usable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open your web browser and navigate to &lt;code&gt;http://localhost:3000&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fplsrqh8v8dq0c58mxa0b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fplsrqh8v8dq0c58mxa0b.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;Congratulations! You just built and ran your first Dockerized application.&lt;/p&gt;

&lt;p&gt;Because the environment is bundled directly into the image, you could send this exact &lt;code&gt;my-node-app&lt;/code&gt; image to a colleague running Windows, a friend on a Mac, or a Linux server on AWS, and it will run flawlessly. No configuration required.&lt;/p&gt;

&lt;p&gt;Next week, we are going to take this a step further and combine it with the CI/CD pipeline we built last time. I will show you how to automatically build this Docker image using GitHub Actions every time you push code.&lt;/p&gt;

&lt;p&gt;Make sure to subscribe to the blog so you don't miss it!&lt;/p&gt;




&lt;p&gt;If this tutorial helped you finally understand Docker, you can &lt;a href="https://buymeacoffee.com/vatul16" rel="noopener noreferrer"&gt;☕ buy me a coffee here&lt;/a&gt; to support my work!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>node</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
