<?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: MysticMc</title>
    <description>The latest articles on DEV Community by MysticMc (@nazmur96).</description>
    <link>https://dev.to/nazmur96</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%2F3770268%2F4a286af1-ae62-4533-9578-04a4bc8fb006.png</url>
      <title>DEV Community: MysticMc</title>
      <link>https://dev.to/nazmur96</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nazmur96"/>
    <language>en</language>
    <item>
      <title>Why Can't My App Connect to My Database?</title>
      <dc:creator>MysticMc</dc:creator>
      <pubDate>Fri, 09 Oct 2026 11:39:55 +0000</pubDate>
      <link>https://dev.to/nazmur96/why-cant-my-app-connect-to-my-database-2i00</link>
      <guid>https://dev.to/nazmur96/why-cant-my-app-connect-to-my-database-2i00</guid>
      <description>&lt;p&gt;&lt;em&gt;A beginner's guide to the 4 checks on AWS&lt;/em&gt;&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%2Frpgb5fl7fqw85x1g6108.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%2Frpgb5fl7fqw85x1g6108.png" alt="Can't connect to your database? 4 gates in order: security group, PostgreSQL listening, pg_hba.conf, username and password, each with the error you see when it fails" width="800" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The big idea first
&lt;/h2&gt;

&lt;p&gt;When your app connects to a database, the connection has to pass &lt;strong&gt;4 checks, one after another&lt;/strong&gt;. If any one fails, the connection fails.&lt;/p&gt;

&lt;p&gt;Think of visiting a friend in a secure apartment building:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Building analogy&lt;/th&gt;
&lt;th&gt;On AWS + PostgreSQL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;The front gate lets you in&lt;/td&gt;
&lt;td&gt;Security group allows the traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Someone is home to answer the door&lt;/td&gt;
&lt;td&gt;PostgreSQL is listening for connections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Your name is on the visitor list&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pg_hba.conf&lt;/code&gt; allows your app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;You know the secret password&lt;/td&gt;
&lt;td&gt;Username and password are correct&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Keep this picture in mind. Everything below explains one of these steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: Security groups (gate 1)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a security group?
&lt;/h3&gt;

&lt;p&gt;A security group is a &lt;strong&gt;firewall for your AWS resources&lt;/strong&gt;, like an EC2 instance (a virtual server). It decides which network traffic is allowed in.&lt;/p&gt;

&lt;p&gt;Two rules to remember:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Everything coming in is blocked by default.&lt;/strong&gt; You add rules to allow what you need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replies are automatic.&lt;/strong&gt; If a request is allowed in, the response is allowed back out. You don't need a separate rule for that.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What's in a rule?
&lt;/h3&gt;

&lt;p&gt;Every inbound rule has 3 parts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Question it answers&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Protocol&lt;/td&gt;
&lt;td&gt;What kind of traffic?&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Port&lt;/td&gt;
&lt;td&gt;Which "door" on the server?&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;Who is allowed to connect?&lt;/td&gt;
&lt;td&gt;Your IP address&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The "Type" dropdown is just a shortcut
&lt;/h3&gt;

&lt;p&gt;When you pick a Type like SSH or HTTP, AWS fills in the protocol and port for you:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Used for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SSH&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;Logging in to a Linux server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;Normal websites&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;443&lt;/td&gt;
&lt;td&gt;Secure websites&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom TCP&lt;/td&gt;
&lt;td&gt;You choose&lt;/td&gt;
&lt;td&gt;Anything else&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;AWS does &lt;strong&gt;not&lt;/strong&gt; check what's actually running on that port. Picking "SSH" just means "open port 22." If your SSH runs on port 2222, pick Custom TCP and type 2222.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: Who can connect? (the source)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What does &lt;code&gt;0.0.0.0/0&lt;/code&gt; mean?
&lt;/h3&gt;

&lt;p&gt;It means &lt;strong&gt;anyone on the internet&lt;/strong&gt; (any IPv4 address).&lt;/p&gt;

&lt;h3&gt;
  
  
  Does that mean anyone can log in?
&lt;/h3&gt;

&lt;p&gt;No. It means anyone can &lt;strong&gt;reach the door and try the lock&lt;/strong&gt;. They still need the right key, such as your SSH private key.&lt;/p&gt;

&lt;p&gt;But it's still risky:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bots&lt;/strong&gt; scan the internet all day and will try to break in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bugs&lt;/strong&gt; in software sometimes let attackers in without a key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Future mistakes&lt;/strong&gt;, like a weak password, become much more dangerous.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Safer options for SSH
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use "My IP" as the source.&lt;/strong&gt; AWS fills in your IP with &lt;code&gt;/32&lt;/code&gt; at the end, which means "only this one address." If your home IP changes, you'll need to update the rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use AWS Session Manager.&lt;/strong&gt; It gives you a terminal on your server without opening port 22 at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 3: Ports
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What happens when you open a port?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Nothing is running on that port&lt;/td&gt;
&lt;td&gt;Connection fails, nothing to talk to&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An app is running on that port&lt;/td&gt;
&lt;td&gt;Traffic can reach that app&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Why big port ranges are dangerous
&lt;/h3&gt;

&lt;p&gt;Opening &lt;code&gt;3000-9000&lt;/code&gt; opens &lt;strong&gt;6,001 ports&lt;/strong&gt;, not just the one you need.&lt;/p&gt;

&lt;p&gt;Today, maybe only your web app is running. Next week you install PostgreSQL on port 5432, and it's suddenly exposed to the internet without you changing anything in AWS.&lt;/p&gt;

&lt;p&gt;Common ports to know:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Usually used by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3000&lt;/td&gt;
&lt;td&gt;Dev web apps, Grafana&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5432&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6379&lt;/td&gt;
&lt;td&gt;Redis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8080&lt;/td&gt;
&lt;td&gt;Jenkins, Tomcat&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; open only the ports you need, only to the sources that need them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Part 4: Connecting an app server to a database server
&lt;/h2&gt;

&lt;p&gt;You have two servers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;App server:&lt;/strong&gt; runs your website&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database server:&lt;/strong&gt; runs PostgreSQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You want the app to reach the database and block everyone else.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option A: allow the app server's IP (works, but annoying)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Port: 5432    Source: 10.0.1.25/32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you replace the app server, it gets a new IP, and you have to update the rule.&lt;/li&gt;
&lt;li&gt;If you add 10 app servers, you need 10 rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Option B: allow a security group (better)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create a security group called &lt;code&gt;app-sg&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Attach it to your app server.&lt;/li&gt;
&lt;li&gt;On the database's security group, allow:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Port: 5432    Source: app-sg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means: "Allow any server wearing the &lt;code&gt;app-sg&lt;/code&gt; badge."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Replace a server?&lt;/strong&gt; Give the new one &lt;code&gt;app-sg&lt;/code&gt;. No rule changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add 10 servers?&lt;/strong&gt; Give them all &lt;code&gt;app-sg&lt;/code&gt;. Still one rule.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;One catch:&lt;/strong&gt; this only works when the app connects to the database's &lt;strong&gt;private IP&lt;/strong&gt; (inside the same network). If it uses the database's public IP, the badge isn't recognized and the connection will time out.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Part 5: The 4 gates in detail
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Gate 1: Security group
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; Is traffic allowed to reach port 5432?&lt;/p&gt;

&lt;p&gt;The database's security group needs a rule allowing TCP 5432 from &lt;code&gt;app-sg&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gate 2: Is PostgreSQL listening?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; Is PostgreSQL accepting connections from other machines?&lt;/p&gt;

&lt;p&gt;By default, PostgreSQL usually listens only to itself (&lt;code&gt;localhost&lt;/code&gt;). Other servers can't reach it.&lt;/p&gt;

&lt;p&gt;To fix it, edit &lt;code&gt;postgresql.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;listen_addresses&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means "listen on all network connections." It does &lt;strong&gt;not&lt;/strong&gt; mean anyone can log in. Gates 3 and 4 still protect you.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 You must &lt;strong&gt;restart&lt;/strong&gt; PostgreSQL after changing this. A reload isn't enough.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Gate 3: PostgreSQL's visitor list (&lt;code&gt;pg_hba.conf&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; Is this app allowed to connect to this database as this user?&lt;/p&gt;

&lt;p&gt;PostgreSQL has its own list of who's allowed, separate from AWS. You need a line in &lt;code&gt;pg_hba.conf&lt;/code&gt; allowing your app server to connect to &lt;code&gt;mydb&lt;/code&gt; as user &lt;code&gt;app&lt;/code&gt;. For example, to allow the app server's whole subnet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# TYPE  DATABASE  USER  ADDRESS        METHOD
host    mydb      app   10.0.1.0/24    scram-sha-256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 After editing, &lt;strong&gt;reload&lt;/strong&gt; PostgreSQL.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Gate 4: Username and password
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; Are the login details correct?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host:     10.0.2.40
Port:     5432
Database: mydb
Username: app
Password: (the correct one)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if gates 1–3 are perfect, a wrong password means no entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 6: Reading error messages
&lt;/h2&gt;

&lt;p&gt;The error message tells you which gate failed. &lt;strong&gt;This is the most useful skill in this guide.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Error message&lt;/th&gt;
&lt;th&gt;What it feels like&lt;/th&gt;
&lt;th&gt;Which gate&lt;/th&gt;
&lt;th&gt;What to check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Connection timed out&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Waits a long time, then gives up&lt;/td&gt;
&lt;td&gt;Gate 1 (network)&lt;/td&gt;
&lt;td&gt;Security group rules, network settings, server firewall&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Connection refused&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fails instantly&lt;/td&gt;
&lt;td&gt;Gate 2&lt;/td&gt;
&lt;td&gt;Is PostgreSQL running? Is it listening on the network?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no pg_hba.conf entry for host...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL answered but said no&lt;/td&gt;
&lt;td&gt;Gate 3&lt;/td&gt;
&lt;td&gt;Your &lt;code&gt;pg_hba.conf&lt;/code&gt; rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;password authentication failed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Got all the way to login&lt;/td&gt;
&lt;td&gt;Gate 4&lt;/td&gt;
&lt;td&gt;Username, password, does the user exist?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Good news hidden in errors:&lt;/strong&gt; the further down the table your error is, the more is already working. A password error means gates 1–3 are fine.&lt;/p&gt;

&lt;p&gt;💡 "Password authentication failed" also appears if the user doesn't exist at all. PostgreSQL does this on purpose so attackers can't guess usernames.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Part 7: Two commands to test
&lt;/h2&gt;

&lt;p&gt;Run these from the app server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 1: Can I reach the port? (gates 1–2)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nc &lt;span class="nt"&gt;-zv&lt;/span&gt; 10.0.2.40 5432
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Success&lt;/strong&gt; means the network path works and something is listening.&lt;/li&gt;
&lt;li&gt;It does &lt;strong&gt;not&lt;/strong&gt; test the password or &lt;code&gt;pg_hba.conf&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Test 2: Can I log in? (all 4 gates)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-h&lt;/span&gt; 10.0.2.40 &lt;span class="nt"&gt;-U&lt;/span&gt; app &lt;span class="nt"&gt;-d&lt;/span&gt; mydb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-h 10.0.2.40&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Database server's address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-U app&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Log in as user &lt;code&gt;app&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-d mydb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Connect to database &lt;code&gt;mydb&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Success&lt;/strong&gt; means all 4 gates pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure&lt;/strong&gt; means you read the error and match it to the table in Part 6.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 8: Troubleshooting checklist
&lt;/h2&gt;

&lt;p&gt;When the app can't connect, don't change random settings. Go in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Security group:&lt;/strong&gt; Does the DB allow port 5432 from &lt;code&gt;app-sg&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network:&lt;/strong&gt; Can the app server reach the DB's private IP? (&lt;code&gt;nc -zv&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL running:&lt;/strong&gt; Is it running and listening on the network?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visitor list:&lt;/strong&gt; Does &lt;code&gt;pg_hba.conf&lt;/code&gt; allow this app, database, and user?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Login:&lt;/strong&gt; Are the username and password correct? (&lt;code&gt;psql&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Quick recap
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;One-line meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Security group&lt;/td&gt;
&lt;td&gt;AWS firewall that controls incoming traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type dropdown&lt;/td&gt;
&lt;td&gt;Shortcut that fills in the port&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0.0.0.0/0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Anyone on the internet. Avoid for SSH and databases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/32&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exactly one IP address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Port range&lt;/td&gt;
&lt;td&gt;Opens every port in the range. Keep it narrow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SG as source&lt;/td&gt;
&lt;td&gt;"Allow anything with this badge"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;listen_addresses&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Makes PostgreSQL listen to the network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pg_hba.conf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL's own visitor list&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The one thing to remember:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Security group → PostgreSQL listening → &lt;code&gt;pg_hba.conf&lt;/code&gt; → Password&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These are 4 separate locks. Fixing one doesn't fix the others. The error message tells you which lock is stuck.&lt;/p&gt;

&lt;h2&gt;
  
  
  Good to know later (skip for now)
&lt;/h2&gt;

&lt;p&gt;Once you're comfortable with the basics, look into these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Network ACLs&lt;/strong&gt;, a second, stricter firewall layer on subnets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outbound rules&lt;/strong&gt; on the app's security group.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IPv6 (&lt;code&gt;::/0&lt;/code&gt;)&lt;/strong&gt;, the IPv6 equivalent of &lt;code&gt;0.0.0.0/0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS RDS&lt;/strong&gt;, a managed database where gates 2 and 3 work a bit differently.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;originally published at &lt;a href="https://github.com/nazmur96/devops-learning-journal/blob/main/networking/why-cant-my-app-connect-to-my-database.md" rel="noopener noreferrer"&gt;https://github.com/nazmur96/devops-learning-journal/blob/main/networking/why-cant-my-app-connect-to-my-database.md&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>postgres</category>
      <category>beginners</category>
      <category>devops</category>
    </item>
    <item>
      <title>How Code Goes from Your Laptop to the User</title>
      <dc:creator>MysticMc</dc:creator>
      <pubDate>Thu, 08 Oct 2026 14:15:27 +0000</pubDate>
      <link>https://dev.to/nazmur96/how-code-goes-from-your-laptop-to-the-user-3m1d</link>
      <guid>https://dev.to/nazmur96/how-code-goes-from-your-laptop-to-the-user-3m1d</guid>
      <description>&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%2Flaewnwbvxfqfj7yjflpv.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%2Flaewnwbvxfqfj7yjflpv.png" alt="Every language, same 4 jobs: entry point, dependencies, build tool and final output for Go, Rust, Java, C#, Python and JavaScript" width="800" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The big idea
&lt;/h2&gt;

&lt;p&gt;Every programming language solves the same four problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Where does the program start?&lt;/strong&gt; → the &lt;em&gt;Entry Point&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How do I get other people's code (libraries)?&lt;/strong&gt; → the &lt;em&gt;Dependency Manager&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How do I turn my code into something ready to ship?&lt;/strong&gt; → the &lt;em&gt;Build Tool&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What do I actually give to the user or server?&lt;/strong&gt; → the &lt;em&gt;Final Output&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A simple analogy is a recipe:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Recipe analogy&lt;/th&gt;
&lt;th&gt;Software&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Entry Point&lt;/td&gt;
&lt;td&gt;"Step 1" of the recipe&lt;/td&gt;
&lt;td&gt;The first file that runs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency Manager&lt;/td&gt;
&lt;td&gt;Going shopping for ingredients&lt;/td&gt;
&lt;td&gt;Downloading libraries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build Tool&lt;/td&gt;
&lt;td&gt;Cooking&lt;/td&gt;
&lt;td&gt;Compiling and packaging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final Output&lt;/td&gt;
&lt;td&gt;The finished dish&lt;/td&gt;
&lt;td&gt;The file you deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The tools have different names in each language, but the job is the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two families of languages
&lt;/h2&gt;

&lt;p&gt;Most of the differences come from one question: &lt;strong&gt;does the final output need a runtime installed to work?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Family A: compiled to a single binary (Go, Rust).&lt;/strong&gt;&lt;br&gt;
The output is one file that runs by itself. You copy it to a server and run it. Nothing else needs to be installed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Family B: needs a runtime (Java, C#, Python, JavaScript).&lt;/strong&gt;&lt;br&gt;
The output only works if a "helper program" is installed on the machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Java needs the &lt;strong&gt;JVM&lt;/strong&gt; (Java Virtual Machine)&lt;/li&gt;
&lt;li&gt;C# needs the &lt;strong&gt;.NET runtime&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Python needs the &lt;strong&gt;Python interpreter&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;JavaScript needs a &lt;strong&gt;browser&lt;/strong&gt; (or Node.js on a server)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this in mind, the rest is easier to remember.&lt;/p&gt;

&lt;h2&gt;
  
  
  Language by language
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. JavaScript (frontend)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Entry Point:&lt;/strong&gt; &lt;code&gt;index.html&lt;/code&gt; loads &lt;code&gt;index.js&lt;/code&gt; (or &lt;code&gt;main.js&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencies:&lt;/strong&gt; npm, yarn, or pnpm, listed in &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Tool:&lt;/strong&gt; Vite or Webpack. These combine many files into a few, make them smaller, and make them work in all browsers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output:&lt;/strong&gt; A folder (usually &lt;code&gt;dist/&lt;/code&gt;) with &lt;code&gt;.js&lt;/code&gt;, &lt;code&gt;.css&lt;/code&gt;, and image files. A web server or CDN serves these to the browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Java
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Entry Point:&lt;/strong&gt; A class with a &lt;code&gt;public static void main()&lt;/code&gt; method, such as &lt;code&gt;Main.java&lt;/code&gt;, or &lt;code&gt;Application.java&lt;/code&gt; in Spring Boot&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencies:&lt;/strong&gt; Maven (&lt;code&gt;pom.xml&lt;/code&gt;) or Gradle (&lt;code&gt;build.gradle&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Tool:&lt;/strong&gt; The same Maven or Gradle. They both download libraries and build.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.jar&lt;/code&gt;: runs directly with &lt;code&gt;java -jar app.jar&lt;/code&gt; (the modern way)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.war&lt;/code&gt;: deployed into a server like Tomcat (the older way)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Python
&lt;/h3&gt;

&lt;p&gt;Python is interpreted. It reads and runs your &lt;code&gt;.py&lt;/code&gt; files directly, so normally there is no real "build" step.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Entry Point:&lt;/strong&gt; &lt;code&gt;main.py&lt;/code&gt; (or whichever script you run first)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencies:&lt;/strong&gt; pip with &lt;code&gt;requirements.txt&lt;/code&gt;, or newer tools like Poetry or uv with &lt;code&gt;pyproject.toml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build/Packaging Tool:&lt;/strong&gt; Only needed in special cases:

&lt;ul&gt;
&lt;li&gt;setuptools or Poetry, to publish a library for others&lt;/li&gt;
&lt;li&gt;PyInstaller, to make a standalone program for people without Python&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output:&lt;/strong&gt; Usually just your source code plus its dependencies. Optionally a package on PyPI (a &lt;code&gt;.whl&lt;/code&gt; file) or a &lt;code&gt;.exe&lt;/code&gt; from PyInstaller.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Go
&lt;/h3&gt;

&lt;p&gt;Go was built by Google to compile fast and to produce one self-contained file. Everything you need comes with Go itself.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Entry Point:&lt;/strong&gt; &lt;code&gt;main.go&lt;/code&gt;, specifically the &lt;code&gt;main()&lt;/code&gt; function inside &lt;code&gt;package main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencies:&lt;/strong&gt; Go Modules (&lt;code&gt;go.mod&lt;/code&gt; and &lt;code&gt;go.sum&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Tool:&lt;/strong&gt; &lt;code&gt;go build&lt;/code&gt;, which is built in&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output:&lt;/strong&gt; One binary file that includes your code and all libraries. No runtime needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Rust
&lt;/h3&gt;

&lt;p&gt;Rust focuses on speed and safety. One tool, Cargo, handles almost everything.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Entry Point:&lt;/strong&gt; &lt;code&gt;src/main.rs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencies:&lt;/strong&gt; Cargo. Libraries are called "crates" and are listed in &lt;code&gt;Cargo.toml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Tool:&lt;/strong&gt; &lt;code&gt;cargo build&lt;/code&gt;, which calls the &lt;code&gt;rustc&lt;/code&gt; compiler for you&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output:&lt;/strong&gt; One fast binary file, or a &lt;code&gt;.wasm&lt;/code&gt; (WebAssembly) file that runs in browsers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. C# / .NET
&lt;/h3&gt;

&lt;p&gt;Modern C# works much like Java: code is compiled for a runtime, not directly for the machine.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Entry Point:&lt;/strong&gt; &lt;code&gt;Program.cs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencies:&lt;/strong&gt; NuGet, listed in the &lt;code&gt;.csproj&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Tool:&lt;/strong&gt; &lt;code&gt;dotnet build&lt;/code&gt; (which uses MSBuild behind the scenes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output:&lt;/strong&gt; A &lt;code&gt;.dll&lt;/code&gt; file that runs on the .NET runtime, or a self-contained executable that bundles the runtime inside&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Entry Point&lt;/th&gt;
&lt;th&gt;Dependency Manager&lt;/th&gt;
&lt;th&gt;Build Tool&lt;/th&gt;
&lt;th&gt;Final Output&lt;/th&gt;
&lt;th&gt;Needs runtime?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JavaScript&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;index.html&lt;/code&gt; / &lt;code&gt;index.js&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;npm / yarn&lt;/td&gt;
&lt;td&gt;Vite / Webpack&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;dist/&lt;/code&gt; folder (JS, CSS, assets)&lt;/td&gt;
&lt;td&gt;Yes (browser)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Main.java&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Maven / Gradle&lt;/td&gt;
&lt;td&gt;Maven / Gradle&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.jar&lt;/code&gt; / &lt;code&gt;.war&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes (JVM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;&lt;code&gt;main.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pip / Poetry / uv&lt;/td&gt;
&lt;td&gt;Usually none (PyInstaller optional)&lt;/td&gt;
&lt;td&gt;Source code, &lt;code&gt;.whl&lt;/code&gt;, or &lt;code&gt;.exe&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes (Python)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;&lt;code&gt;main.go&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go Modules&lt;/td&gt;
&lt;td&gt;&lt;code&gt;go build&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Single binary&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;&lt;code&gt;main.rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cargo&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cargo build&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Single binary / &lt;code&gt;.wasm&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C#&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Program.cs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;NuGet&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dotnet build&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.dll&lt;/code&gt; / executable&lt;/td&gt;
&lt;td&gt;Usually (.NET)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Two things people often get wrong
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python &lt;code&gt;.pyc&lt;/code&gt; files are not a deployment output.&lt;/strong&gt; Python creates them automatically as a speed cache. You don't ship them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In Java and Rust, one tool does two jobs.&lt;/strong&gt; Maven/Gradle and Cargo both download dependencies &lt;em&gt;and&lt;/em&gt; build. Go is similar with the &lt;code&gt;go&lt;/code&gt; command. Only JavaScript clearly separates the two (npm + Vite).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this matters for DevOps
&lt;/h2&gt;

&lt;p&gt;This is the knowledge you need when writing Dockerfiles and CI pipelines. The &lt;strong&gt;Final Output&lt;/strong&gt; decides what goes into your container image:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Go / Rust:&lt;/strong&gt; Copy just the binary into a tiny image (&lt;code&gt;scratch&lt;/code&gt; or distroless). The result is very small and secure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Java:&lt;/strong&gt; Build the &lt;code&gt;.jar&lt;/code&gt;, then copy it into an image that has a Java runtime (JRE).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python:&lt;/strong&gt; Use a Python base image, install dependencies, and copy in the source code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript frontend:&lt;/strong&gt; Build the &lt;code&gt;dist/&lt;/code&gt; folder, then serve it with Nginx. Node isn't needed in the final image.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why &lt;strong&gt;multi-stage Docker builds&lt;/strong&gt; are common: stage 1 has all the build tools, and stage 2 contains only the final output.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;originally published at &lt;a href="https://github.com/nazmur96/devops-learning-journal/blob/main/foundations/how-code-goes-from-laptop-to-user.md" rel="noopener noreferrer"&gt;https://github.com/nazmur96/devops-learning-journal/blob/main/foundations/how-code-goes-from-laptop-to-user.md&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Kubernetes Networking, Explained Simply</title>
      <dc:creator>MysticMc</dc:creator>
      <pubDate>Thu, 08 Oct 2026 10:47:47 +0000</pubDate>
      <link>https://dev.to/nazmur96/kubernetes-networking-explained-simply-p3o</link>
      <guid>https://dev.to/nazmur96/kubernetes-networking-explained-simply-p3o</guid>
      <description>&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%2Fljwte5fyqzwcbv3nty6x.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%2Fljwte5fyqzwcbv3nty6x.png" alt="Kubernetes networking is 5 jobs, not one tool fight: Pod networking, front door, routing rules, rule follower, service mesh" width="800" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  0. Two words you need first
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pod:&lt;/strong&gt; a running copy of your app. Pods come and go, and their addresses change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service:&lt;/strong&gt; a stable name and address that always points to the right Pods, even when they change. Other parts of the system talk to the Service, not to Pods directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's all the Kubernetes you need for the rest of this.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The big idea: sort tools by job, not by rivalry
&lt;/h2&gt;

&lt;p&gt;When you see a list like ALB, NGINX, Traefik, Cilium, F5, it looks like they're all fighting each other. Most of them aren't. They do different jobs.&lt;/p&gt;

&lt;p&gt;So don't ask "which tool is best?" Ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What job needs doing, and which tool will do it?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. The 5 networking jobs
&lt;/h2&gt;

&lt;p&gt;Think of a Kubernetes cluster like an office building:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Simple question&lt;/th&gt;
&lt;th&gt;Building analogy&lt;/th&gt;
&lt;th&gt;Common tools&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Pod networking (CNI)&lt;/td&gt;
&lt;td&gt;How does each Pod get an address and talk to others?&lt;/td&gt;
&lt;td&gt;Wiring and room numbers inside the building&lt;/td&gt;
&lt;td&gt;AWS VPC CNI, Cilium, Calico, Flannel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;External load balancer&lt;/td&gt;
&lt;td&gt;Who receives traffic from the internet first?&lt;/td&gt;
&lt;td&gt;The front door&lt;/td&gt;
&lt;td&gt;AWS ALB, AWS NLB, F5, HAProxy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Routing rules (written)&lt;/td&gt;
&lt;td&gt;How do I describe where traffic should go?&lt;/td&gt;
&lt;td&gt;The sign on the wall: "Sales → 2nd floor"&lt;/td&gt;
&lt;td&gt;Ingress (older), Gateway API (newer)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Routing implementation&lt;/td&gt;
&lt;td&gt;Who actually follows those rules?&lt;/td&gt;
&lt;td&gt;The receptionist who reads the sign and sends people the right way&lt;/td&gt;
&lt;td&gt;AWS Load Balancer Controller, NGINX, Traefik, Kong, Cilium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Service-to-service control (mesh)&lt;/td&gt;
&lt;td&gt;How do services inside talk to each other safely and reliably?&lt;/td&gt;
&lt;td&gt;Internal security and hallway rules&lt;/td&gt;
&lt;td&gt;Istio, Linkerd, Cilium&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;CNI stands for Container Network Interface. It's just the name for "the plugin that gives Pods their networking."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Job 3 is just rules written down. Gateway API does nothing on its own. It needs a tool from Job 4 to make the rules real.&lt;/p&gt;

&lt;p&gt;You pick one tool per job, and only for the jobs you actually need. A service mesh (Job 5), for example, is optional.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Gateway API instead of Ingress?
&lt;/h3&gt;

&lt;p&gt;Both are ways to write routing rules. The difference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ingress&lt;/strong&gt; can only describe simple things, like "this URL goes to that Service." For anything fancier (splitting traffic 90/10, matching on headers), every tool invented its own custom add-ons, so your rules only worked with that one tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gateway API&lt;/strong&gt; builds those features into the standard itself, so your rules work the same way no matter which tool follows them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The popular community ingress-nginx controller was retired in March 2026, which is pushing many teams to switch to Gateway API now.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. One key idea: "L4" vs "L7" front doors
&lt;/h2&gt;

&lt;p&gt;You'll see these terms everywhere. They describe how much a load balancer looks at before passing traffic on.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Mail analogy&lt;/th&gt;
&lt;th&gt;AWS example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L4&lt;/td&gt;
&lt;td&gt;Passes connections through without looking inside. Fast and simple.&lt;/td&gt;
&lt;td&gt;A mail sorter that only reads the building address&lt;/td&gt;
&lt;td&gt;NLB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L7&lt;/td&gt;
&lt;td&gt;Opens the request and reads it (the URL, headers, etc.), then decides where to send it. Smarter.&lt;/td&gt;
&lt;td&gt;A mail sorter that also reads "Attention: Sales department"&lt;/td&gt;
&lt;td&gt;ALB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Keep this in mind. It explains why the projects below pick different front doors.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The example app
&lt;/h2&gt;

&lt;p&gt;We'll build the same small online shop three different ways:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;frontend   →  the website
api        →  the backend logic
payment    →  handles payments
database   →  stores data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;About the database: it isn't part of the incoming-traffic story. Internet users never reach it directly. Only the api and payment services talk to it. In AWS setups it often lives outside the cluster as a managed service (Amazon RDS). So in the pictures below, it sits at the very end.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Project 1: The "let AWS do it" setup
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mindset:&lt;/strong&gt; "I use Kubernetes, but I want AWS to handle as much networking as possible."&lt;/p&gt;

&lt;p&gt;Very common in AWS-heavy companies.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pod networking&lt;/td&gt;
&lt;td&gt;AWS VPC CNI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Front door&lt;/td&gt;
&lt;td&gt;AWS ALB (L7)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routing rules&lt;/td&gt;
&lt;td&gt;Gateway API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who makes the rules real&lt;/td&gt;
&lt;td&gt;AWS Load Balancer Controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service-to-service&lt;/td&gt;
&lt;td&gt;None yet (no mesh)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            INTERNET
                │
                ▼
            AWS ALB  ◄──── AWS Load Balancer Controller
                │          (reads your Gateway API rules
                │           and sets up the ALB to match)
                ▼
        ┌───────┴───────┐
        ▼               ▼
    frontend           api
                        │
                        ▼
                     payment
                        │
                        ▼
                    database
                 (Amazon RDS,
               outside the cluster)

Underneath: AWS VPC CNI gives every Pod a real IP from your AWS VPC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How a request travels.&lt;/strong&gt; A user visits &lt;code&gt;https://shop.example.com/api&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The request hits the AWS ALB.&lt;/li&gt;
&lt;li&gt;Because the ALB is L7, it reads the URL and sees "/api goes to the api service."&lt;/li&gt;
&lt;li&gt;The request reaches the api Pod.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Worth knowing:&lt;/strong&gt; the AWS Load Balancer Controller is not in the path the traffic takes. It works behind the scenes: it reads your Gateway API rules and sets up the ALB to match them. The traffic itself goes ALB → Pod.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When would you add a service mesh later?&lt;/strong&gt; When you start needing things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;encrypted traffic between your own services (called mTLS)&lt;/li&gt;
&lt;li&gt;automatic retries when a service briefly fails&lt;/li&gt;
&lt;li&gt;detailed numbers on how each service is talking to the others&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Until then, skipping the mesh keeps things simpler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why choose this setup:&lt;/strong&gt; less to install and maintain yourself, because AWS runs the hard parts. The downside is that you're tied to AWS.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Project 2: AWS outside, open-source inside
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mindset:&lt;/strong&gt; "We use AWS for the cloud, but we want the Kubernetes networking to not depend on AWS."&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pod networking&lt;/td&gt;
&lt;td&gt;Cilium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Front door&lt;/td&gt;
&lt;td&gt;AWS NLB (L4)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routing rules&lt;/td&gt;
&lt;td&gt;Gateway API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who makes the rules real&lt;/td&gt;
&lt;td&gt;Cilium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service-to-service&lt;/td&gt;
&lt;td&gt;Cilium&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            INTERNET
                │
                ▼
            AWS NLB        (L4: just passes traffic in)
                │
                ▼
             Cilium        (reads the URL and follows
                │           your Gateway API rules)
     ┌──────────┼──────────┐
     ▼          ▼          ▼
 frontend      api      payment
     └──────────┴──────────┘
                │
                ▼
            database

   Cilium also controls traffic and
   security between the services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice: one tool, Cilium, does many jobs at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pod networking (CNI)&lt;/li&gt;
&lt;li&gt;Network policy (who is allowed to talk to whom)&lt;/li&gt;
&lt;li&gt;Following your Gateway API rules&lt;/li&gt;
&lt;li&gt;Both simple (L4) and smart (L7) traffic handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why the NLB instead of the ALB?&lt;/strong&gt; This is where L4 vs L7 matters. The NLB is a simple front door that just passes traffic through, and Cilium makes the smart routing decisions. If you used the ALB, it would do the routing itself, and that's exactly the AWS-specific part you're trying to avoid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why choose this setup:&lt;/strong&gt; if you ever move off AWS, most of your Kubernetes networking (Cilium + Gateway API) comes with you. Only the NLB needs replacing.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Project 3: Mostly open-source, runs anywhere
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mindset:&lt;/strong&gt; "The same platform has to run on AWS, Azure, our own servers, bare metal and private cloud."&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pod networking&lt;/td&gt;
&lt;td&gt;Cilium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Front door&lt;/td&gt;
&lt;td&gt;HAProxy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routing rules&lt;/td&gt;
&lt;td&gt;Gateway API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who makes the rules real&lt;/td&gt;
&lt;td&gt;Traefik&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service-to-service&lt;/td&gt;
&lt;td&gt;Istio&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            INTERNET
                │
                ▼
            HAProxy        OUTSIDE the cluster
                │          (stable front door)
   - - - - - - -│- - - - - - - - - - - - -
                ▼
            Traefik        INSIDE the cluster
                │          (follows your Gateway API rules)
     ┌──────────┼──────────┐
     ▼          ▼          ▼
 frontend      api      payment
     └──────────┴──────────┘
                │
                ▼
            database

   Istio controls traffic between services
   (encryption, retries, monitoring)

Underneath: Cilium gives every Pod networking
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why two proxies in a row (HAProxy, then Traefik)?&lt;/strong&gt; They do different jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HAProxy&lt;/strong&gt; sits outside the cluster. Its job is to be a stable front door with a fixed address. If a server inside the cluster dies, HAProxy just stops sending traffic there. It doesn't need to understand Kubernetes at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traefik&lt;/strong&gt; sits inside the cluster. It understands Kubernetes, reads your Gateway API rules, and knows which Pods are running right now.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of HAProxy as the building's front door and Traefik as the receptionist inside.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why choose this setup:&lt;/strong&gt; almost nothing here is tied to a cloud provider, so the same design works everywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cost:&lt;/strong&gt; you run and maintain everything yourself. There are more moving parts, more to learn and more that can break.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. All three side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Project 1 (AWS-heavy)&lt;/th&gt;
&lt;th&gt;Project 2 (mixed)&lt;/th&gt;
&lt;th&gt;Project 3 (open-source)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pod networking&lt;/td&gt;
&lt;td&gt;AWS VPC CNI&lt;/td&gt;
&lt;td&gt;Cilium&lt;/td&gt;
&lt;td&gt;Cilium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Front door&lt;/td&gt;
&lt;td&gt;AWS ALB (L7)&lt;/td&gt;
&lt;td&gt;AWS NLB (L4)&lt;/td&gt;
&lt;td&gt;HAProxy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routing rules&lt;/td&gt;
&lt;td&gt;Gateway API&lt;/td&gt;
&lt;td&gt;Gateway API&lt;/td&gt;
&lt;td&gt;Gateway API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rule follower&lt;/td&gt;
&lt;td&gt;AWS LB Controller&lt;/td&gt;
&lt;td&gt;Cilium&lt;/td&gt;
&lt;td&gt;Traefik&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service-to-service&lt;/td&gt;
&lt;td&gt;None (yet)&lt;/td&gt;
&lt;td&gt;Cilium&lt;/td&gt;
&lt;td&gt;Istio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tied to AWS?&lt;/td&gt;
&lt;td&gt;A lot&lt;/td&gt;
&lt;td&gt;A little&lt;/td&gt;
&lt;td&gt;Barely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Effort to run&lt;/td&gt;
&lt;td&gt;Lowest&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Highest&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice: Gateway API stays the same in all three. That's its whole point. It's a standard way to write routing rules, and you can swap out the tool that follows them.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Where does MetalLB fit?
&lt;/h2&gt;

&lt;p&gt;People sometimes call MetalLB "Kubernetes' version of HAProxy." That's not quite right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem it solves:&lt;/strong&gt; in Kubernetes, you can create a Service of &lt;code&gt;type: LoadBalancer&lt;/code&gt;, which basically means "give me an address the outside world can reach."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On AWS, Azure or GCP, the cloud automatically creates a load balancer and gives you that address.&lt;/li&gt;
&lt;li&gt;On bare metal or your own servers, nobody does this. The Service just sits there stuck on &lt;code&gt;&amp;lt;pending&amp;gt;&lt;/code&gt; forever.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MetalLB fills that gap. It hands out outside-reachable addresses to your LoadBalancer Services, doing the job the cloud would normally do.&lt;/p&gt;

&lt;p&gt;So a better way to think of it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;MetalLB = "the cloud's load-balancer feature, for clusters that don't have a cloud."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;How it connects to Project 3:&lt;/strong&gt; when Project 3 runs on bare metal, something has to give the entry point a stable address. MetalLB is a common way to do that. It can be used alongside HAProxy, or in simpler setups it can replace the need for a separate outside proxy, giving Traefik its address directly.&lt;/p&gt;

&lt;p&gt;In the 5-job list, MetalLB belongs to Job 2 (the front door), but only for clusters without a cloud provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. One-sentence summary
&lt;/h2&gt;

&lt;p&gt;Split networking into 5 jobs, pick one tool per job, and remember that the tools mostly work together rather than compete. How much you hand over to AWS versus run yourself is the real choice you're making.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;originally published at &lt;a href="https://github.com/nazmur96/devops-learning-journal/blob/main/networking/kubernetes-networking-explained.md" rel="noopener noreferrer"&gt;https://github.com/nazmur96/devops-learning-journal/blob/main/networking/kubernetes-networking-explained.md&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>networking</category>
      <category>aws</category>
      <category>devops</category>
    </item>
    <item>
      <title>Getting AWS Secrets into EKS Pods with ESO and Pod Identity</title>
      <dc:creator>MysticMc</dc:creator>
      <pubDate>Mon, 05 Oct 2026 13:54:14 +0000</pubDate>
      <link>https://dev.to/nazmur96/getting-aws-secrets-into-eks-pods-with-eso-and-pod-identity-55if</link>
      <guid>https://dev.to/nazmur96/getting-aws-secrets-into-eks-pods-with-eso-and-pod-identity-55if</guid>
      <description>&lt;p&gt;So we have an app/microservice/pod which needs access to a secret that we&lt;br&gt;
stored in AWS Secrets Manager, let's say the database password.&lt;/p&gt;

&lt;p&gt;We don't want our app to talk to AWS at all. We want the app to just find the&lt;br&gt;
password inside Kubernetes as a normal K8s Secret. So we bring in a middleman&lt;br&gt;
to fetch it for us: &lt;strong&gt;ESO&lt;/strong&gt; (External Secrets Operator).&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%2Fqy1myuo5lja3epsjhnov.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%2Fqy1myuo5lja3epsjhnov.png" alt="ESO and EKS Pod Identity: the app reads a K8s Secret, ESO fetches it from Secrets Manager using temporary credentials from the Pod Identity Agent" width="800" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Part 1: Setup (done once)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1: Install ESO in the cluster
&lt;/h3&gt;

&lt;p&gt;We install External Secrets Operator, usually with Helm, into its own namespace&lt;br&gt;
(&lt;code&gt;external-secrets&lt;/code&gt;). This creates the ESO pods and a ServiceAccount for them,&lt;br&gt;
also called &lt;code&gt;external-secrets&lt;/code&gt;. Now we have a courier living in our cluster,&lt;br&gt;
but it has no AWS ID card yet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EKS
 ├── App pod
 └── ESO pod  (ServiceAccount: external-secrets)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Install the Pod Identity Agent
&lt;/h3&gt;

&lt;p&gt;We install the EKS Pod Identity Agent add-on. It runs a small helper on every&lt;br&gt;
node. Its job is to hand out AWS credentials to pods that are allowed to have&lt;br&gt;
them. Think of it as the ID card office.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3: Create an IAM role for ESO
&lt;/h3&gt;

&lt;p&gt;We create an IAM role, say &lt;code&gt;ESO-Secrets-Reader&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Permissions policy:&lt;/strong&gt; "you can read the secret &lt;code&gt;my-app/database&lt;/code&gt;"
(&lt;code&gt;GetSecretValue&lt;/code&gt;, &lt;code&gt;DescribeSecret&lt;/code&gt;, limited to only the secrets ESO needs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust policy:&lt;/strong&gt; "I trust EKS Pod Identity (&lt;code&gt;pods.eks.amazonaws.com&lt;/code&gt;) to
hand me out to pods."&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Step 4: Connect the role to ESO's ServiceAccount
&lt;/h3&gt;

&lt;p&gt;We create a Pod Identity association that tells EKS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace: external-secrets + ServiceAccount: external-secrets
                    ↓
            IAM role: ESO-Secrets-Reader
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meaning: "any pod running with this ServiceAccount gets this role." Now ESO has&lt;br&gt;
an ID card.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; if ESO was already running before you created the association,&lt;br&gt;
restart the ESO pods. Pod Identity sets up the credential wiring when a pod&lt;br&gt;
starts, so old pods won't pick it up.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Part 2: Telling ESO what to do
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 5: Create a SecretStore
&lt;/h3&gt;

&lt;p&gt;This tells ESO &lt;strong&gt;where&lt;/strong&gt; to look: "AWS Secrets Manager, region&lt;br&gt;
&lt;code&gt;eu-central-1&lt;/code&gt;." There are no access keys in here. ESO uses its Pod Identity&lt;br&gt;
credentials automatically.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 6: Create an ExternalSecret
&lt;/h3&gt;

&lt;p&gt;This tells ESO &lt;strong&gt;what&lt;/strong&gt; to fetch: "Get &lt;code&gt;my-app/database&lt;/code&gt; from that store,&lt;br&gt;
create a K8s Secret called &lt;code&gt;database-secret&lt;/code&gt; in my app's namespace, and check&lt;br&gt;
for changes every hour."&lt;/p&gt;
&lt;h2&gt;
  
  
  Part 3: What happens automatically
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 7: ESO gets its credentials
&lt;/h3&gt;

&lt;p&gt;ESO needs to call AWS, so the AWS SDK inside ESO asks the Pod Identity Agent&lt;br&gt;
for credentials. The agent checks the association, gets temporary credentials&lt;br&gt;
for &lt;code&gt;ESO-Secrets-Reader&lt;/code&gt;, and gives them to ESO. They expire and get refreshed&lt;br&gt;
automatically. No access keys are stored anywhere.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 8: ESO fetches the secret
&lt;/h3&gt;

&lt;p&gt;ESO calls Secrets Manager with those credentials and gets the password.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 9: ESO creates the K8s Secret
&lt;/h3&gt;

&lt;p&gt;ESO creates a normal Kubernetes Secret called &lt;code&gt;database-secret&lt;/code&gt;. If someone&lt;br&gt;
changes the password in AWS later, ESO notices on its next refresh and updates&lt;br&gt;
it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 10: The app reads the secret
&lt;/h3&gt;

&lt;p&gt;The app's YAML just says "give me &lt;code&gt;DB_PASSWORD&lt;/code&gt; from Secret &lt;code&gt;database-secret&lt;/code&gt;,"&lt;br&gt;
as an env variable or a file. The app has no IAM role, no AWS credentials, and&lt;br&gt;
no idea AWS exists.&lt;/p&gt;
&lt;h2&gt;
  
  
  The picture to remember
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pod Identity Agent ──gives temp creds──► ESO pod
                                          │
                                          │ fetch
                                          ▼
                                   Secrets Manager
                                          │
                                          │ password
                                          ▼
                                    ESO creates
                                          │
                                          ▼
                                 K8s Secret (database-secret)
                                          │
                                          │ reads
                                          ▼
                                       App pod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  The whole thing in one line
&lt;/h2&gt;

&lt;p&gt;Install ESO and the Pod Identity Agent → give ESO an AWS role through a Pod&lt;br&gt;
Identity association → tell ESO where to look (SecretStore) and what to fetch&lt;br&gt;
(ExternalSecret) → ESO copies the secret into a K8s Secret → the app reads it.&lt;/p&gt;
&lt;h2&gt;
  
  
  The analogy
&lt;/h2&gt;

&lt;p&gt;Secrets Manager is a bank vault. ESO is a courier we hire (Step 1). The Pod&lt;br&gt;
Identity Agent is the office that issues ID cards (Step 2). The IAM role is the&lt;br&gt;
permission slip that says what the courier can pick up (Step 3), and the&lt;br&gt;
association assigns that slip to our courier (Step 4). We tell the courier&lt;br&gt;
which bank (SecretStore) and which box (ExternalSecret). The courier picks up&lt;br&gt;
the cash and puts it in a locker inside our building (the K8s Secret), and our&lt;br&gt;
app just opens the locker.&lt;/p&gt;


&lt;h1&gt;
  
  
  Hands-on: step-by-step template
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; an app in EKS reads a secret from AWS Secrets Manager without having&lt;br&gt;
any AWS credentials itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt; a running EKS cluster, plus &lt;code&gt;aws&lt;/code&gt;, &lt;code&gt;kubectl&lt;/code&gt;, and &lt;code&gt;helm&lt;/code&gt;&lt;br&gt;
installed and configured.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 0: Set your variables
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLUSTER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-cluster
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eu-central-1
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ACCOUNT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws sts get-caller-identity &lt;span class="nt"&gt;--query&lt;/span&gt; Account &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Step 1: Create a secret in AWS Secrets Manager
&lt;/h3&gt;

&lt;p&gt;This is the secret our app needs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws secretsmanager create-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; my-app/database &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="nv"&gt;$AWS_REGION&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-string&lt;/span&gt; &lt;span class="s1"&gt;'{"username":"admin","password":"S3cr3t!"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Install ESO (the courier)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add external-secrets https://charts.external-secrets.io
helm repo update

helm &lt;span class="nb"&gt;install &lt;/span&gt;external-secrets external-secrets/external-secrets &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; external-secrets &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--create-namespace&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates the ESO pods and a ServiceAccount named &lt;code&gt;external-secrets&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Install the Pod Identity Agent (the ID card office)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws eks create-addon &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster-name&lt;/span&gt; &lt;span class="nv"&gt;$CLUSTER_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--addon-name&lt;/span&gt; eks-pod-identity-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check that it's running on every node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get daemonset eks-pod-identity-agent &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Create an IAM role for ESO (the permission slip)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Trust policy:&lt;/strong&gt; lets EKS Pod Identity hand this role to pods.&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; trust-policy.json &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "pods.eks.amazonaws.com" },
      "Action": ["sts:AssumeRole", "sts:TagSession"]
    }
  ]
}
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;aws iam create-role &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role-name&lt;/span&gt; ESO-Secrets-Reader &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--assume-role-policy-document&lt;/span&gt; file://trust-policy.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Permissions policy:&lt;/strong&gt; allows reading only our app's secrets.&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; permissions-policy.json &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue",
        "secretsmanager:DescribeSecret"
      ],
      "Resource": "arn:aws:secretsmanager:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AWS_REGION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ACCOUNT_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;:secret:my-app/*"
    }
  ]
}
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;aws iam put-role-policy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role-name&lt;/span&gt; ESO-Secrets-Reader &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-name&lt;/span&gt; read-my-app-secrets &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-document&lt;/span&gt; file://permissions-policy.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your secret is encrypted with a custom KMS key instead of the AWS default,&lt;br&gt;
also allow &lt;code&gt;kms:Decrypt&lt;/code&gt; on that key.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 5: Connect the role to ESO's ServiceAccount (give the courier the slip)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws eks create-pod-identity-association &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster-name&lt;/span&gt; &lt;span class="nv"&gt;$CLUSTER_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; external-secrets &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service-account&lt;/span&gt; external-secrets &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role-arn&lt;/span&gt; arn:aws:iam::&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ACCOUNT_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;:role/ESO-Secrets-Reader
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then restart ESO so its pods pick up the new identity. Pods only get the&lt;br&gt;
credential wiring when they start.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl rollout restart deployment &lt;span class="nt"&gt;-n&lt;/span&gt; external-secrets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Tell ESO where to look (ClusterSecretStore)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# cluster-secret-store.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external-secrets.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterSecretStore&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-secrets-manager&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;aws&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretsManager&lt;/span&gt;
      &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu-central-1&lt;/span&gt;   &lt;span class="c1"&gt;# change to your region&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's no &lt;code&gt;auth&lt;/code&gt; section and no access keys. ESO automatically uses the&lt;br&gt;
credentials Pod Identity gives it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; cluster-secret-store.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're on an older ESO version, use &lt;code&gt;external-secrets.io/v1beta1&lt;/code&gt; instead of&lt;br&gt;
&lt;code&gt;v1&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7: Tell ESO what to fetch (ExternalSecret)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# external-secret.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external-secrets.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ExternalSecret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;database-secret&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;refreshInterval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1h&lt;/span&gt;            &lt;span class="c1"&gt;# how often ESO checks AWS for changes&lt;/span&gt;
  &lt;span class="na"&gt;secretStoreRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-secrets-manager&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterSecretStore&lt;/span&gt;
  &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;database-secret&lt;/span&gt;        &lt;span class="c1"&gt;# the K8s Secret ESO will create&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;username&lt;/span&gt;        &lt;span class="c1"&gt;# key name inside the K8s Secret&lt;/span&gt;
      &lt;span class="na"&gt;remoteRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app/database&lt;/span&gt;     &lt;span class="c1"&gt;# secret name in AWS&lt;/span&gt;
        &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;username&lt;/span&gt;       &lt;span class="c1"&gt;# JSON field inside it&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
      &lt;span class="na"&gt;remoteRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app/database&lt;/span&gt;
        &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create namespace my-app
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; external-secret.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 8: The app reads the K8s Secret (opens the locker)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;busybox&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sh"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;echo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;App&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;started;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sleep&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3600"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DB_USERNAME&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;database-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;username&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DB_PASSWORD&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;database-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; app.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app has no IAM role and no AWS credentials. It just reads a normal K8s&lt;br&gt;
Secret.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 9: Verify it works
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Did ESO sync the secret? Look for STATUS = SecretSynced, READY = True&lt;/span&gt;
kubectl get externalsecret &lt;span class="nt"&gt;-n&lt;/span&gt; my-app

&lt;span class="c"&gt;# 2. Does the K8s Secret exist with the right value?&lt;/span&gt;
kubectl get secret database-secret &lt;span class="nt"&gt;-n&lt;/span&gt; my-app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.password}'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# 3. Does the app see it?&lt;/span&gt;
kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; my-app deploy/my-app &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;printenv &lt;/span&gt;DB_USERNAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ExternalSecret shows &lt;code&gt;AccessDenied&lt;/code&gt;:&lt;/strong&gt; check the association exists
(&lt;code&gt;aws eks list-pod-identity-associations --cluster-name $CLUSTER_NAME&lt;/code&gt;),
confirm the namespace and ServiceAccount names match exactly, and restart the
ESO pods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ResourceNotFound&lt;/code&gt;:&lt;/strong&gt; the secret name or region in AWS doesn't match
&lt;code&gt;remoteRef.key&lt;/code&gt; or the store's region.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret changed in AWS but the app still has the old value:&lt;/strong&gt; ESO updated
the K8s Secret, but env vars are only read when a pod starts. Restart the
app, mount the secret as a volume instead, or use a tool like Reloader to
restart pods automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cleanup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete namespace my-app
helm uninstall external-secrets &lt;span class="nt"&gt;-n&lt;/span&gt; external-secrets
aws eks delete-pod-identity-association &lt;span class="nt"&gt;--cluster-name&lt;/span&gt; &lt;span class="nv"&gt;$CLUSTER_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--association-id&lt;/span&gt; &amp;lt;id-from-list-command&amp;gt;
aws iam delete-role-policy &lt;span class="nt"&gt;--role-name&lt;/span&gt; ESO-Secrets-Reader &lt;span class="nt"&gt;--policy-name&lt;/span&gt; read-my-app-secrets
aws iam delete-role &lt;span class="nt"&gt;--role-name&lt;/span&gt; ESO-Secrets-Reader
aws secretsmanager delete-secret &lt;span class="nt"&gt;--secret-id&lt;/span&gt; my-app/database &lt;span class="nt"&gt;--force-delete-without-recovery&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;originally published at &lt;a href="https://github.com/nazmur96/devops-sre-authentication/blob/main/posts/eso-eks-pod-identity.md" rel="noopener noreferrer"&gt;https://github.com/nazmur96/devops-sre-authentication/blob/main/posts/eso-eks-pod-identity.md&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>aws</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>HD-101 — The first pull request</title>
      <dc:creator>MysticMc</dc:creator>
      <pubDate>Tue, 25 Aug 2026 14:08:50 +0000</pubDate>
      <link>https://dev.to/nazmur96/hd-101-the-first-pull-request-2fep</link>
      <guid>https://dev.to/nazmur96/hd-101-the-first-pull-request-2fep</guid>
      <description>&lt;h1&gt;
  
  
  HD-101 — The first pull request
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Builds:&lt;/strong&gt; nothing. &lt;strong&gt;Teaches:&lt;/strong&gt; why the next six tickets exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ticket
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Add a feature to the helpdesk CLI. Don't push to &lt;code&gt;main&lt;/code&gt; — open a pull request.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What you do
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git switch &lt;span class="nt"&gt;-c&lt;/span&gt; hd-101-priority-filter
&lt;span class="c"&gt;# ... write the code ...&lt;/span&gt;
git add &lt;span class="nt"&gt;-A&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat: filter tickets by priority"&lt;/span&gt;
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin hd-101-priority-filter

gh &lt;span class="nb"&gt;pr &lt;/span&gt;create &lt;span class="nt"&gt;--base&lt;/span&gt; main &lt;span class="nt"&gt;--head&lt;/span&gt; hd-101-priority-filter &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"HD-101: filter tickets by priority"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Adds --priority to the list command."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then merge it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happened
&lt;/h2&gt;

&lt;p&gt;The PR merged. Green tick, satisfying. And &lt;strong&gt;nothing had checked anything.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There were no tests running, no required reviewers, no status checks. The PR was a&lt;br&gt;
change of scenery, not a gate. Anyone could have merged anything.&lt;/p&gt;

&lt;p&gt;That is the entire point of this ticket. You are supposed to feel how empty it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two words that cause the most confusion
&lt;/h2&gt;

&lt;p&gt;Every PR has a &lt;strong&gt;base&lt;/strong&gt; and a &lt;strong&gt;head&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Word&lt;/th&gt;
&lt;th&gt;Means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;base&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Destination.&lt;/strong&gt; Where the code lands.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;head&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Source.&lt;/strong&gt; Where the code comes from.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;--base main --head hd-101-priority-filter&lt;/code&gt; reads: &lt;em&gt;this branch ends up in &lt;code&gt;main&lt;/code&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Get these backwards and you will open a PR proposing to merge &lt;code&gt;main&lt;/code&gt; into your&lt;br&gt;
feature branch, which looks almost right and is completely wrong. It happens to&lt;br&gt;
everyone at least once. It happened here — see &lt;a href="https://github.com/nazmur96/git-promotion-ladder/blob/main/docs/mistakes.md" rel="noopener noreferrer"&gt;mistakes.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also:&lt;/strong&gt; a PR's &lt;code&gt;head&lt;/code&gt; has nothing to do with git's &lt;code&gt;HEAD&lt;/code&gt; pointer. Same word,&lt;br&gt;
unrelated meanings. Git's &lt;code&gt;HEAD&lt;/code&gt; is "where I am standing right now"; a PR's head is&lt;br&gt;
"the branch being proposed".&lt;/p&gt;

&lt;h2&gt;
  
  
  What to look at before moving on
&lt;/h2&gt;

&lt;p&gt;Open the merged PR and look at the checks section. It's empty. Remember that.&lt;/p&gt;




&lt;p&gt;Next: &lt;strong&gt;&lt;a href="https://github.com/nazmur96/git-promotion-ladder/blob/main/docs/hd-102-continuous-integration.md" rel="noopener noreferrer"&gt;HD-102 — Continuous integration&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;originally published at &lt;a href="https://github.com/nazmur96/git-promotion-ladder/blob/main/docs/hd-101-first-pull-request.md" rel="noopener noreferrer"&gt;https://github.com/nazmur96/git-promotion-ladder/blob/main/docs/hd-101-first-pull-request.md&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>5 Docker Scenarios Every Developer Should Practice (With Fixes &amp; Best Practices)</title>
      <dc:creator>MysticMc</dc:creator>
      <pubDate>Tue, 21 Apr 2026 20:56:35 +0000</pubDate>
      <link>https://dev.to/nazmur96/5-docker-scenarios-every-developer-should-practice-with-fixes-best-practices-3l70</link>
      <guid>https://dev.to/nazmur96/5-docker-scenarios-every-developer-should-practice-with-fixes-best-practices-3l70</guid>
      <description>&lt;p&gt;So you know the basics of Docker — &lt;code&gt;docker run&lt;/code&gt;, &lt;code&gt;docker build&lt;/code&gt;, maybe some &lt;code&gt;docker compose up&lt;/code&gt;. But do you know what happens when things break?&lt;/p&gt;

&lt;p&gt;This guide walks you through &lt;strong&gt;5 real-world Docker scenarios&lt;/strong&gt; that will sharpen your skills around debugging, security, storage, and production-readiness.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scenario 1: The Broken Build 🔨
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Goal
&lt;/h3&gt;

&lt;p&gt;Fix a broken Dockerfile, then optimize it to be 10x smaller and production-ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;app.py&lt;/code&gt;&lt;/strong&gt;&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello from Docker!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;requirements.txt&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Flask==2.3.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ❌ The Broken Dockerfile
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu:latest&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;python3

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /opt/app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; python /opt/app/app.py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tasks
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Troubleshoot the Build
&lt;/h4&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; broken-app &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are &lt;strong&gt;at least 3 issues&lt;/strong&gt; hiding in that Dockerfile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pip&lt;/code&gt; is never installed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apt-get install&lt;/code&gt; is missing the &lt;code&gt;-y&lt;/code&gt; flag (hangs waiting for input)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COPY&lt;/code&gt; happens before dependency install — busting the cache on every change&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Fix Layer Caching
&lt;/h4&gt;

&lt;p&gt;Copy &lt;code&gt;requirements.txt&lt;/code&gt; first, install deps, &lt;em&gt;then&lt;/em&gt; copy the rest of the app. This way, changing &lt;code&gt;app.py&lt;/code&gt; won't trigger a full &lt;code&gt;pip install&lt;/code&gt; on every build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /opt/app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Optimize the Image
&lt;/h4&gt;

&lt;p&gt;Switch from &lt;code&gt;ubuntu:latest&lt;/code&gt; to a slim base:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /opt/app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 5000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "app.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Target:&lt;/strong&gt; &amp;lt; 150MB image size ✅&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Security Hardening
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run as non-root&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; 1001&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run with a read-only filesystem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--read-only&lt;/span&gt; &lt;span class="nt"&gt;--tmpfs&lt;/span&gt; /tmp myapp:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Scenario 2: The Ghost Data 👻
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Goal
&lt;/h3&gt;

&lt;p&gt;Understand Docker's storage lifecycle and how to make data actually persist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tasks
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Run an Ephemeral Container
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; web-test &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Write Data Inside the Container
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; web-test bash
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /usr/share/nginx/html/test.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit &lt;code&gt;http://localhost:8080/test.txt&lt;/code&gt; — it's there!&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Destroy &amp;amp; Recreate
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker stop web-test
docker &lt;span class="nb"&gt;rm &lt;/span&gt;web-test
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; web-test2 &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The file is gone. Why?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Because the container filesystem is &lt;strong&gt;ephemeral&lt;/strong&gt; — it only lives as long as the container does.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  4. Named Volume Persistence
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker volume create web-data

docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; web-test &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; web-data:/usr/share/nginx/html &lt;span class="se"&gt;\&lt;/span&gt;
  nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  5. Verify Persistence
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; web-test bash
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"persistent data"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /usr/share/nginx/html/test.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stop and remove the container, then start a new one with the same volume — your file will still be there. 🎉&lt;/p&gt;

&lt;h4&gt;
  
  
  6. Cleanup
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker system prune &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: &lt;code&gt;system prune&lt;/code&gt; removes stopped containers, dangling images, and unused networks — but &lt;strong&gt;not named volumes&lt;/strong&gt; unless you add &lt;code&gt;--volumes&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Scenario 3: The Flaky Database Connection 🐘
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Goal
&lt;/h3&gt;

&lt;p&gt;Fix service startup dependency issues in Docker Compose.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Broken &lt;code&gt;docker-compose.yml&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5000:5000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_HOST=postgres&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;

  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=secret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tasks
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Run the Stack
&lt;/h4&gt;



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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; &lt;code&gt;depends_on&lt;/code&gt; only waits for the container to &lt;em&gt;start&lt;/em&gt;, not for Postgres to be &lt;em&gt;ready to accept connections&lt;/em&gt;. Your web app crashes on startup.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Add a Healthcheck to Postgres
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=secret&lt;/span&gt;
  &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;postgres"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
    &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Fix &lt;code&gt;depends_on&lt;/code&gt; with a Condition
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your web service won't start until Postgres passes its healthcheck. ✅&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Move Secrets to &lt;code&gt;.env&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;.env&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;secret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=${POSTGRES_PASSWORD}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  5. Keep Secrets Out of Git
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;.gitignore&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;.&lt;span class="n"&gt;env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never commit &lt;code&gt;.env&lt;/code&gt; files. Ever.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scenario 4: The Evil Image Scanner 🔍
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Goal
&lt;/h3&gt;

&lt;p&gt;Find and fix vulnerabilities hiding in your Docker images.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tasks
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Build a Vulnerable Image
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:1.21.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Install Trivy
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;aquasecurity/trivy/trivy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or on Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sfL&lt;/span&gt; https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Scan the Image
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;trivy image nginx:1.21.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see a wall of &lt;strong&gt;HIGH&lt;/strong&gt; and &lt;strong&gt;CRITICAL&lt;/strong&gt; CVEs. This is what ships to production when nobody checks.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Fix the Base Image
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:1.25-bookworm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or go even lighter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:alpine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  5. Rescan
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;trivy image nginx:1.25-bookworm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected:&lt;/strong&gt; 0 CRITICAL vulnerabilities ✅&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make image scanning part of your CI/CD pipeline — not an afterthought.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Scenario 5: The Limited Resource Box 📦
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Goal
&lt;/h3&gt;

&lt;p&gt;Run containers safely under strict resource constraints and enable offline portability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tasks
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Build the Optimized Image
&lt;/h4&gt;

&lt;p&gt;Use the hardened image from Scenario 1.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Run with Resource Limits
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;256m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cpus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.5 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--read-only&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tmpfs&lt;/span&gt; /tmp &lt;span class="se"&gt;\&lt;/span&gt;
  myapp:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--memory=256m&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hard cap on RAM usage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--cpus=0.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Limits to half a CPU core&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--read-only&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prevents filesystem writes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--tmpfs /tmp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mounts writable in-memory temp dir&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  3. Verify the Restrictions
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &amp;lt;container_id&amp;gt; bash
&lt;span class="nb"&gt;touch&lt;/span&gt; /etc/test
&lt;span class="c"&gt;# Permission denied ✅&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Monitor resource usage live:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Export the Image
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker save &lt;span class="nt"&gt;-o&lt;/span&gt; myapp.tar myapp:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  5. Simulate an Offline Machine
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker rmi myapp:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  6. Restore the Image
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker load &lt;span class="nt"&gt;-i&lt;/span&gt; myapp.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  7. Run Without Internet
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run myapp:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works fully offline. 🚀 Great for air-gapped environments, demos, or CI runners without registry access.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You've Learned 🎓
&lt;/h2&gt;

&lt;p&gt;After completing all five scenarios, you now have hands-on experience with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Dockerfile debugging &amp;amp; optimization&lt;/strong&gt; — fixing real build errors and shrinking image sizes&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Layer caching strategies&lt;/strong&gt; — speeding up builds without reinstalling dependencies&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Volume persistence&lt;/strong&gt; — understanding ephemeral vs. persistent storage&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Compose healthchecks&lt;/strong&gt; — preventing race conditions between services&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Image vulnerability scanning&lt;/strong&gt; — catching CVEs before they reach production&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Runtime security hardening&lt;/strong&gt; — non-root users, read-only filesystems, resource limits&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Offline container portability&lt;/strong&gt; — shipping containers without a registry&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Want more? Drop a comment and I can turn this into a full GitHub repo with solutions, or expand each scenario into its own deep-dive post.&lt;/em&gt;&lt;/p&gt;



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

&lt;/div&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>containers</category>
      <category>beginners</category>
    </item>
    <item>
      <title>⚙️ My first CI/CD pipeline: From git push to production in 10 minutes</title>
      <dc:creator>MysticMc</dc:creator>
      <pubDate>Thu, 02 Apr 2026 19:45:56 +0000</pubDate>
      <link>https://dev.to/nazmur96/my-first-cicd-pipeline-from-git-push-to-production-in-10-minutes-1h4f</link>
      <guid>https://dev.to/nazmur96/my-first-cicd-pipeline-from-git-push-to-production-in-10-minutes-1h4f</guid>
      <description>&lt;p&gt;Remember the old days?&lt;/p&gt;

&lt;p&gt;You write code. You test it manually (maybe). You open FileZilla. You drag and drop files to a server. You pray.&lt;/p&gt;

&lt;p&gt;Then someone yells: "Who deployed on a Friday?!"&lt;/p&gt;

&lt;p&gt;Those days are dead.&lt;/p&gt;

&lt;p&gt;CI/CD pipelines are the reason modern developers ship code 10x faster with 90% fewer bugs. And the best part? You can set one up in 10 minutes. For free.&lt;/p&gt;

&lt;p&gt;Let me show you.&lt;/p&gt;




&lt;p&gt;What is CI/CD? (The 30-second explanation)&lt;/p&gt;

&lt;p&gt;CI = Continuous Integration – Every time you push code, automatically test it.&lt;/p&gt;

&lt;p&gt;CD = Continuous Delivery/Deployment – If tests pass, automatically deploy it.&lt;/p&gt;

&lt;p&gt;In practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push origin main
   ↓
&lt;span class="o"&gt;[&lt;/span&gt;Automatic] Run tests
   ↓
&lt;span class="o"&gt;[&lt;/span&gt;Automatic] Build the app
   ↓
&lt;span class="o"&gt;[&lt;/span&gt;Automatic] Deploy to server
   ↓
You get a notification: &lt;span class="s2"&gt;"✅ Deployment successful"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You never touch a server. You never run scp or FTP. You just push code.&lt;/p&gt;




&lt;p&gt;The 10-Minute Challenge&lt;/p&gt;

&lt;p&gt;By the end of this guide, you will have:&lt;/p&gt;

&lt;p&gt;· A simple Node.js/Express app (or your own project)&lt;br&gt;
· A GitHub repository&lt;br&gt;
· A GitHub Actions workflow that:&lt;br&gt;
  · Runs tests automatically&lt;br&gt;
  · Deploys to a free hosting service (Render.com – no credit card)&lt;/p&gt;

&lt;p&gt;Total time: ~10 minutes. I'll wait.&lt;/p&gt;



&lt;p&gt;What You'll Need (All Free)&lt;/p&gt;

&lt;p&gt;Tool Why&lt;br&gt;
GitHub account Hosts your code + runs CI/CD&lt;br&gt;
Render.com account Free hosting (similar to Heroku)&lt;br&gt;
A simple app We'll make one in 2 minutes&lt;/p&gt;

&lt;p&gt;No credit card required. No servers to manage.&lt;/p&gt;



&lt;p&gt;Step 1: The App (2 minutes)&lt;/p&gt;

&lt;p&gt;Create a new folder and a simple Express app:&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;mkdir &lt;/span&gt;my-cicd-app
&lt;span class="nb"&gt;cd &lt;/span&gt;my-cicd-app
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create index.js:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&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;express&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&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;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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="s1"&gt;CI/CD works! 🚀&lt;/span&gt;&lt;span class="dl"&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/health&lt;/span&gt;&lt;span class="dl"&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&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="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// A simple test endpoint&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/add/:a/:b&lt;/span&gt;&lt;span class="dl"&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;a&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;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&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="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="s2"&gt;`App running on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&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;Create a test file test.js (we'll use Node's built-in assert):&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assert&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;assert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Simple tests that will run in CI&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="s1"&gt;Running tests...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Test 1: Addition works&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&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;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&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="s1"&gt;✅ Test 1 passed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Test 2: String test&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello&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="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&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="s1"&gt;✅ Test 2 passed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&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="s1"&gt;🎉 All tests passed!&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;Update package.json with a test script:&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;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node test.js"&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;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;p&gt;Test it locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;test&lt;/span&gt;          &lt;span class="c"&gt;# Should show "All tests passed!"&lt;/span&gt;
npm start         &lt;span class="c"&gt;# Visit http://localhost:3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Step 2: Push to GitHub (2 minutes)&lt;/p&gt;

&lt;p&gt;Create a repository on GitHub (don't initialize with README – we have our own).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git init
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Initial commit"&lt;/span&gt;
git remote add origin https://github.com/YOUR_USERNAME/my-cicd-app.git
git branch &lt;span class="nt"&gt;-M&lt;/span&gt; main
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Step 3: Create the CI/CD Pipeline (3 minutes)&lt;/p&gt;

&lt;p&gt;This is where the magic happens. GitHub Actions reads a file in .github/workflows/ and runs it.&lt;/p&gt;

&lt;p&gt;Create this folder and file:&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;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; .github/workflows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create .github/workflows/deploy.yml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Production&lt;/span&gt;

&lt;span class="c1"&gt;# When should this run?&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Every push to main branch&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="c1"&gt;# Also allow manual trigger&lt;/span&gt;

&lt;span class="c1"&gt;# What environment variables do we need?&lt;/span&gt;
&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;NODE_VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;18'&lt;/span&gt;

&lt;span class="c1"&gt;# Jobs run in parallel by default&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# JOB 1: Test&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Tests&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;  &lt;span class="c1"&gt;# Free Linux runner&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Node.js&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.NODE_VERSION }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;  &lt;span class="c1"&gt;# ci is faster and stricter than install&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;

  &lt;span class="c1"&gt;# JOB 2: Deploy (only if tests passed)&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Render&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;  &lt;span class="c1"&gt;# Wait for test job to succeed&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Render&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;johnbeynon/render-deploy-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;service-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.RENDER_SERVICE_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;api-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.RENDER_API_KEY }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this does:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On every git push to main:&lt;/li&gt;
&lt;li&gt;Spin up a fresh Linux machine (free)&lt;/li&gt;
&lt;li&gt;Run npm test&lt;/li&gt;
&lt;li&gt;If tests pass → deploy to Render&lt;/li&gt;
&lt;li&gt;If tests fail → stop (no deploy)&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;Step 4: Hosting on Render (3 minutes)&lt;/p&gt;

&lt;p&gt;Render is like the old Heroku (but with a free tier that actually works).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to render.com and sign up with GitHub.&lt;/li&gt;
&lt;li&gt;Click "New +" → "Web Service"&lt;/li&gt;
&lt;li&gt;Connect your GitHub repository (my-cicd-app)&lt;/li&gt;
&lt;li&gt;Fill in the settings:
· Name: my-cicd-app
· Environment: Node
· Build Command: npm install
· Start Command: npm start
· Plan: Free&lt;/li&gt;
&lt;li&gt;Click "Create Web Service"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Render will deploy your app once manually. Wait for the green "Live" badge.&lt;/p&gt;

&lt;p&gt;You'll get a URL like: &lt;a href="https://my-cicd-app.onrender.com" rel="noopener noreferrer"&gt;https://my-cicd-app.onrender.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Visit it. You should see your JSON message.&lt;/p&gt;




&lt;p&gt;Step 5: Connect GitHub Actions to Render (2 minutes)&lt;/p&gt;

&lt;p&gt;Now we need to give GitHub Actions permission to deploy to Render.&lt;/p&gt;

&lt;p&gt;Get your Render API Key:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to Render Dashboard → Account Settings (top right)&lt;/li&gt;
&lt;li&gt;Scroll to "API Keys" → Click "Create API Key"&lt;/li&gt;
&lt;li&gt;Name it github-actions → Copy the key (starts with rnd_)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Get your Render Service ID:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to your Web Service page on Render&lt;/li&gt;
&lt;li&gt;Look at the URL: &lt;a href="https://dashboard.render.com/web/srv-abc123def" rel="noopener noreferrer"&gt;https://dashboard.render.com/web/srv-abc123def&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The srv-abc123def part is your Service ID&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Add them as GitHub Secrets:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to your GitHub repository → Settings → Secrets and variables → Actions&lt;/li&gt;
&lt;li&gt;Click "New repository secret"&lt;/li&gt;
&lt;li&gt;Add RENDER_API_KEY → paste your API key&lt;/li&gt;
&lt;li&gt;Add RENDER_SERVICE_ID → paste your Service ID&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;Step 6: Trigger Your First Pipeline (1 minute)&lt;/p&gt;

&lt;p&gt;Make a small change to trigger the pipeline:&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;# Change the message in index.js&lt;/span&gt;
res.json&lt;span class="o"&gt;({&lt;/span&gt; message: &lt;span class="s1"&gt;'My first CI/CD pipeline! 🎉'&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c"&gt;# Commit and push&lt;/span&gt;
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Test CI/CD pipeline"&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now go to your GitHub repository → Actions tab.&lt;/p&gt;

&lt;p&gt;You'll see a workflow running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Deploy to Production
  ├── Run Tests (yellow → green)
  └── Deploy to Render (yellow → green)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When both turn green ✅, visit your Render URL. Your new message is live.&lt;/p&gt;

&lt;p&gt;You just did CI/CD. 🚀&lt;/p&gt;




&lt;p&gt;What Just Happened? (The Diagram)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your laptop
    │
    │ git push origin main
    ▼
GitHub (Actions)
    │
    │ Spins up runner
    ▼
┌─────────────────────────────────────┐
│  CI Pipeline (Test)                 │
│  ├── npm ci                         │
│  ├── npm test                       │
│  └── ✅ All tests passed            │
└─────────────────────────────────────┘
    │
    │ Tests pass → trigger CD
    ▼
┌─────────────────────────────────────┐
│  CD Pipeline (Deploy)               │
│  ├── Call Render API                │
│  ├── Render pulls code              │
│  ├── npm install                    │
│  ├── npm start                      │
│  └── ✅ Live at your URL            │
└─────────────────────────────────────┘
    │
    ▼
Production server (Render)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Common Variations (Adapt to Your Stack)&lt;/p&gt;

&lt;p&gt;For a Python (Django/Flask) app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/deploy.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Python&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v4&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.11'&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;pip install -r requirements.txt&lt;/span&gt;
    &lt;span class="s"&gt;pip install pytest&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pytest&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;# Render handles Python automatically&lt;/span&gt;
    &lt;span class="s"&gt;# Or use: flyctl deploy, railway up, etc.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a React/Vite static site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Netlify&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nwtgck/actions-netlify@v2&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;publish-dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;./dist'&lt;/span&gt;
    &lt;span class="na"&gt;production-branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;NETLIFY_AUTH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NETLIFY_TOKEN }}&lt;/span&gt;
    &lt;span class="na"&gt;NETLIFY_SITE_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NETLIFY_SITE_ID }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a Docker container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Docker image&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker build -t myapp .&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Push to Docker Hub&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin&lt;/span&gt;
    &lt;span class="s"&gt;docker push myusername/myapp:latest&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to DigitalOcean&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appleboy/ssh-action@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.HOST }}&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.USERNAME }}&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_KEY }}&lt;/span&gt;
    &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker pull myusername/myapp &amp;amp;&amp;amp; docker-compose up -d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Pro Tips (From Someone Who Broke Production)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Always test before deploying (duh)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The needs: test line ensures you never deploy broken code. Add a failing test and watch the pipeline stop.&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;// Add this to test.js – it will FAIL&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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="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="s1"&gt;This line never runs&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;Push it. Watch the pipeline fail at the test stage. Deploy never happens. You just saved production.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use npm ci instead of npm install&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;· npm install can update package-lock.json (unexpected changes)&lt;br&gt;
· npm ci installs exactly what's in package-lock.json (reproducible builds)&lt;/p&gt;

&lt;p&gt;Always use npm ci in CI.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a "staging" environment&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For real projects, deploy to staging first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;     &lt;span class="c1"&gt;# Deploys to staging&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;production&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Deploys to production&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add notifications&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Get a Slack/Discord message when deploy finishes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Send Slack notification&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;slackapi/slack-github-action@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"text": "✅ Deployed to production!"&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;SLACK_WEBHOOK_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SLACK_WEBHOOK }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Manual approval for production (for serious teams)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;deploy-prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;  &lt;span class="c1"&gt;# Requires approval in GitHub&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to production&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./deploy.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then go to GitHub → Settings → Environments → Add protection rule.&lt;/p&gt;




&lt;p&gt;Troubleshooting (When Things Go Wrong)&lt;/p&gt;

&lt;p&gt;Symptom Most likely fix&lt;br&gt;
Workflow doesn't trigger Check branch name: main vs master&lt;br&gt;
Tests fail but work locally Different Node version? Add .nvmrc file&lt;br&gt;
Deploy step fails Wrong Render Service ID or API key&lt;br&gt;
"Module not found" Forgot npm ci step?&lt;br&gt;
"Permission denied" GitHub Actions needs write permissions → Settings → Actions → General → Workflow permissions&lt;/p&gt;



&lt;p&gt;What You Just Learned&lt;/p&gt;

&lt;p&gt;✅ CI = Automatic testing on every push&lt;br&gt;
✅ CD = Automatic deployment if tests pass&lt;br&gt;
✅ GitHub Actions = Free CI/CD runners&lt;br&gt;
✅ Render = Free hosting with API deploys&lt;br&gt;
✅ Secrets = Store API keys safely&lt;/p&gt;

&lt;p&gt;You can now add CI/CD to any project in ~10 minutes.&lt;/p&gt;



&lt;p&gt;Next Steps (Level Up)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add more tests – Unit tests, integration tests&lt;/li&gt;
&lt;li&gt;Add a database – Render has free PostgreSQL&lt;/li&gt;
&lt;li&gt;Add a staging environment – Deploy to staging first, then production&lt;/li&gt;
&lt;li&gt;Add end-to-end tests – Cypress or Playwright&lt;/li&gt;
&lt;li&gt;Try other platforms – Vercel, Netlify, Railway, Fly.io&lt;/li&gt;
&lt;/ol&gt;



&lt;p&gt;The Ultimate Test&lt;/p&gt;

&lt;p&gt;Break something on purpose:&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;// Add this to index.js – a hidden bug&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/broken&lt;/span&gt;&lt;span class="dl"&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Oops&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// This will crash the server&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Push it. Watch the pipeline deploy it (tests passed, right?). Then visit /broken and watch it fail.&lt;/p&gt;

&lt;p&gt;Now fix it. Push again. Watch the pipeline fix production automatically.&lt;/p&gt;

&lt;p&gt;That's the power of CI/CD. You're not afraid to deploy anymore. Because if something breaks, you fix it and push again. No panic. No Friday deploys. Just code.&lt;/p&gt;




&lt;p&gt;Your 10-Minute Challenge Recap&lt;/p&gt;

&lt;p&gt;· Created an Express app&lt;br&gt;
· Pushed to GitHub&lt;br&gt;
· Created .github/workflows/deploy.yml&lt;br&gt;
· Signed up for Render&lt;br&gt;
· Added API keys as GitHub Secrets&lt;br&gt;
· Made a change and watched the pipeline run&lt;/p&gt;

&lt;p&gt;You did it. 🎉&lt;/p&gt;

&lt;p&gt;Now go add CI/CD to your real project. Your future self (and your team) will thank you.&lt;/p&gt;




&lt;p&gt;What stack do you want to see a CI/CD example for next? Drop a comment (Django, Spring Boot, Next.js, Go, Rust...).&lt;/p&gt;

&lt;p&gt;Follow for more DevOps-for-beginners content.&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>devops</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>🚪 Authentication vs. Authorization – The Bouncer Analogy (Clear up the confusion forever)</title>
      <dc:creator>MysticMc</dc:creator>
      <pubDate>Thu, 02 Apr 2026 19:36:53 +0000</pubDate>
      <link>https://dev.to/nazmur96/authentication-vs-authorization-the-bouncer-analogy-clear-up-the-confusion-forever-5118</link>
      <guid>https://dev.to/nazmur96/authentication-vs-authorization-the-bouncer-analogy-clear-up-the-confusion-forever-5118</guid>
      <description>&lt;p&gt;I can't tell you how many times I've seen these two words used interchangeably.&lt;/p&gt;

&lt;p&gt;Even experienced developers sometimes slip up:&lt;/p&gt;

&lt;p&gt;"We need to add Google Auth so users can authorize with their email."&lt;/p&gt;

&lt;p&gt;No. That's authentication.&lt;/p&gt;

&lt;p&gt;"The JWT token authenticates whether the user is an admin."&lt;/p&gt;

&lt;p&gt;No. That's authorization.&lt;/p&gt;

&lt;p&gt;They sound similar. They work together. But they are not the same thing.&lt;/p&gt;

&lt;p&gt;And mixing them up leads to security holes, confused code, and embarrassing bugs.&lt;/p&gt;

&lt;p&gt;Here's the one analogy that makes it stick forever.&lt;/p&gt;




&lt;p&gt;The Bouncer Analogy 🚪&lt;/p&gt;

&lt;p&gt;Imagine you're at a nightclub.&lt;/p&gt;

&lt;p&gt;Step 1: Authentication (Getting past the front door)&lt;/p&gt;

&lt;p&gt;You walk up to the bouncer. He asks:&lt;/p&gt;

&lt;p&gt;"Do you have an ID?"&lt;/p&gt;

&lt;p&gt;You show your driver's license. He checks the photo. He scans it. He confirms:&lt;/p&gt;

&lt;p&gt;"Okay, you are who you say you are. You're on the guest list. Come in."&lt;/p&gt;

&lt;p&gt;That's authentication. You proved your identity. You're in the club.&lt;/p&gt;

&lt;p&gt;Authentication answers: "Are you who you claim to be?"&lt;/p&gt;




&lt;p&gt;Step 2: Authorization (Getting into VIP)&lt;/p&gt;

&lt;p&gt;Now you're inside the club. You see the VIP section – better drinks, better view, actual seats that aren't sticky.&lt;/p&gt;

&lt;p&gt;You try to walk in. Another bouncer stops you:&lt;/p&gt;

&lt;p&gt;"Whoa there. This is VIP. Do you have a wristband?"&lt;/p&gt;

&lt;p&gt;You show your black wristband (which you paid $500 for). He checks it and says:&lt;/p&gt;

&lt;p&gt;"Cool. You're authorized. Go ahead."&lt;/p&gt;

&lt;p&gt;That's authorization. Your identity is already confirmed. Now we're checking what you're allowed to do.&lt;/p&gt;

&lt;p&gt;Authorization answers: "Are you allowed to do this specific thing?"&lt;/p&gt;




&lt;p&gt;The Table That Finally Makes It Click&lt;/p&gt;

&lt;p&gt;Authentication Authorization&lt;br&gt;
Question it answers "Who are you?" "What can you do?"&lt;br&gt;
When it happens First (at login) After (on each request)&lt;br&gt;
What it proves Identity Permissions&lt;br&gt;
How it works Passwords, biometrics, security keys Roles, permissions, policies&lt;br&gt;
Tells you "You are Bob" "Bob can delete posts"&lt;br&gt;
Bouncer analogy Checking your ID at the door Checking your VIP wristband&lt;br&gt;
HTTP Status Code 401 Unauthorized (ironically named) 403 Forbidden&lt;/p&gt;

&lt;p&gt;Side note: Yes, HTTP 401 says "Unauthorized" but actually means "unauthenticated." It's a historical mistake that confuses everyone. Just remember: 401 = Login first. 403 = You're logged in but not allowed.&lt;/p&gt;



&lt;p&gt;Real Web Examples&lt;/p&gt;

&lt;p&gt;Example 1: Logging into Gmail&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Authentication: You type your email + password. Google checks it's really you. (Success → you get a session cookie or JWT token)&lt;/li&gt;
&lt;li&gt;Authorization: You try to delete an email. Google checks: "Does this user have 'delete' permission for this email?" (Yes, it's your own email)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Same user, same login, but authorization happens on every single action.&lt;/p&gt;



&lt;p&gt;Example 2: Google Drive sharing&lt;/p&gt;

&lt;p&gt;· Authentication: You log into your Google account.&lt;br&gt;
· Authorization: You try to open a shared document. Google checks: "Is this user on the 'can view' list?" If yes → authorized. If no → 403 error.&lt;/p&gt;

&lt;p&gt;You are authenticated (Google knows it's you), but not authorized (you don't own that doc).&lt;/p&gt;



&lt;p&gt;Example 3: Admin dashboard&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;// Authentication: Did the user log in?&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please log in first&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Authorization: Is the logged-in user an admin?&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Admins only&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// If we reach here, user is BOTH authenticated AND authorized&lt;/span&gt;
&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome to the admin panel&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;See the difference? Two checks, two different purposes.&lt;/p&gt;




&lt;p&gt;The Technologies (What actually powers authN and authZ)&lt;/p&gt;

&lt;p&gt;Now let's connect the theory to the actual code you'll write.&lt;/p&gt;

&lt;p&gt;Authentication Technologies (Proving who you are)&lt;/p&gt;

&lt;p&gt;Technology What it does When to use&lt;br&gt;
Passwords + Hash (bcrypt) User provides secret; you compare hashed version Most apps (standard)&lt;br&gt;
JWT (JSON Web Token) Encoded token containing user ID + signature Stateless APIs, mobile apps&lt;br&gt;
Sessions (with cookies) Server stores user data; cookie acts as key Traditional web apps (Rails, PHP, Node with express-session)&lt;br&gt;
OAuth 2.0 "Login with Google/GitHub/Facebook" Letting third parties prove identity&lt;br&gt;
Biometrics Fingerprint, FaceID, passkeys Mobile apps, high-security&lt;br&gt;
2FA / MFA Password + one-time code from phone Any app with sensitive data&lt;/p&gt;

&lt;p&gt;Authentication result: You get a credential (session cookie, JWT token, API key) that you send with every subsequent request.&lt;/p&gt;



&lt;p&gt;Authorization Technologies (What you're allowed to do)&lt;/p&gt;

&lt;p&gt;Technology What it does When to use&lt;br&gt;
RBAC (Role-Based Access Control) Users have roles (admin, editor, viewer); roles have permissions Most business apps (simplest)&lt;br&gt;
ABAC (Attribute-Based Access Control) Permissions based on attributes (time of day, user location, resource owner) Complex systems (healthcare, finance)&lt;br&gt;
ACL (Access Control Lists) Each resource has a list of who can access it File systems, legacy apps&lt;br&gt;
Policies (Casbin, OPA) Declarative rules: "user can edit post if user.id == post.authorId" Fine-grained permissions&lt;/p&gt;

&lt;p&gt;Authorization result: A yes/no answer for each action. No tokens. Just logic.&lt;/p&gt;



&lt;p&gt;The Confusion: JWT, Sessions, Cookies, localStorage&lt;/p&gt;

&lt;p&gt;This is where beginners get lost. Let me untangle it.&lt;/p&gt;

&lt;p&gt;What stores your authentication state?&lt;/p&gt;

&lt;p&gt;After you log in, you need to remember that you're authenticated for future requests.&lt;/p&gt;

&lt;p&gt;Option 1: Sessions (Server-side)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. You log in.
2. Server creates a session record in its database (or Redis).
3. Server sends you a cookie containing a session ID.
4. You send that cookie with every request.
5. Server looks up the session ID to know who you are.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Option 2: JWT (Client-side)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. You log in.
2. Server creates a JWT token (contains your user ID, expiration, signature).
3. Server sends you the token.
4. You store it in localStorage or an HTTP-only cookie.
5. You send the token in the Authorization header with every request.
6. Server verifies the signature (no database lookup needed).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which one should you use?&lt;/p&gt;

&lt;p&gt;Sessions JWT&lt;br&gt;
Where is data stored? Server (database/Redis) Client (token itself)&lt;br&gt;
Can you revoke instantly? ✅ Yes (delete session from DB) ❌ No (token valid until expiration)&lt;br&gt;
Scales how? Need shared session store (Redis) Stateless (any server can verify)&lt;br&gt;
Best for... Traditional web apps Mobile apps, microservices, SPAs&lt;/p&gt;

&lt;p&gt;My advice for beginners: Start with sessions + HTTP-only cookies. It's simpler and more secure by default. Move to JWT only when you have a reason (e.g., mobile app, separate API server).&lt;/p&gt;



&lt;p&gt;localStorage vs. cookies (Security warning!)&lt;/p&gt;

&lt;p&gt;This is critical.&lt;/p&gt;

&lt;p&gt;Storage method Accessible by JavaScript? Vulnerable to XSS? HttpOnly option?&lt;br&gt;
localStorage ✅ Yes (any script can read it) ❌ Yes (high risk) ❌ No&lt;br&gt;
Regular cookie ✅ Yes ❌ Yes ❌ No&lt;br&gt;
HttpOnly cookie ❌ No (JS cannot read it) ✅ No (much safer) ✅ Yes&lt;/p&gt;

&lt;p&gt;Rule of thumb:&lt;/p&gt;

&lt;p&gt;· Never store authentication tokens in localStorage if you care about security. Any XSS vulnerability = attacker steals every user's token.&lt;br&gt;
· Use HttpOnly cookies for sessions or JWTs. They can't be read by JavaScript.&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;// BAD (vulnerable to XSS)&lt;/span&gt;
&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userJWT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// GOOD (more secure)&lt;/span&gt;
&lt;span class="c1"&gt;// Set cookie on server with:&lt;/span&gt;
&lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Cookie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;xyz&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;HttpOnly&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;Secure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;SameSite&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;Strict&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;OAuth 2.0 – The special case&lt;/p&gt;

&lt;p&gt;OAuth is often called "authentication," but that's not quite right.&lt;/p&gt;

&lt;p&gt;OAuth is actually authorization that implies authentication.&lt;/p&gt;

&lt;p&gt;Here's how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You click "Login with Google."&lt;/li&gt;
&lt;li&gt;Google asks: "This app wants to see your email and name. Allow?"&lt;/li&gt;
&lt;li&gt;You say yes.&lt;/li&gt;
&lt;li&gt;Google tells your app: "This user is &lt;a href="mailto:bob@gmail.com"&gt;bob@gmail.com&lt;/a&gt;" (authentication info) AND "Here's a token to access their Google Drive" (authorization).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;OAuth solves: "Let me prove who I am without giving you my password, and also let me give you limited access to my data."&lt;/p&gt;

&lt;p&gt;Common confusion: People call OAuth "social login" (authentication), but OAuth was designed for delegated authorization (like "let this app post to my Twitter"). The login part is a bonus.&lt;/p&gt;




&lt;p&gt;Common Real-World Scenarios&lt;/p&gt;

&lt;p&gt;Scenario 1: You're logged into Netflix but can't watch a specific movie&lt;/p&gt;

&lt;p&gt;· Authentication: ✅ You logged in. Netflix knows it's you.&lt;br&gt;
· Authorization: ❌ Your plan doesn't include that movie (or it's region-locked).&lt;/p&gt;

&lt;p&gt;Error message: "This title is not available in your region." (403 Forbidden)&lt;/p&gt;



&lt;p&gt;Scenario 2: You try to delete a comment on Reddit that isn't yours&lt;/p&gt;

&lt;p&gt;· Authentication: ✅ You're logged in.&lt;br&gt;
· Authorization: ❌ You didn't write that comment.&lt;/p&gt;

&lt;p&gt;Error message: "You cannot delete this comment." (403)&lt;/p&gt;



&lt;p&gt;Scenario 3: You open a private GitHub repo without logging in&lt;/p&gt;

&lt;p&gt;· Authentication: ❌ GitHub has no idea who you are.&lt;br&gt;
· Authorization: ⏸️ Not even checked because you're not authenticated.&lt;/p&gt;

&lt;p&gt;Error message: "Not Found" (404 – GitHub does this on purpose to hide private repos exist). But technically it's a 401.&lt;/p&gt;



&lt;p&gt;The 5-Minute Implementation (Node.js + Express)&lt;/p&gt;

&lt;p&gt;Here's a minimal example showing both concepts:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&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;express&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Fake database&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alice@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alice123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&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="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bob@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bob123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;viewer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&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;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Post 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;authorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&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="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Post 2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;authorId&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="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// ---------- AUTHENTICATION ----------&lt;/span&gt;
&lt;span class="c1"&gt;// Login endpoint – proves who you are&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid credentials&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// 401 = unauthenticated&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Create a session (in real life, use express-session)&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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="s1"&gt;Logged in!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Check authentication middleware&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Please log in first&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ---------- AUTHORIZATION ----------&lt;/span&gt;
&lt;span class="c1"&gt;// Check authorization middleware (role-based)&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Admins only&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// 403 = forbidden&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Check authorization (ownership)&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isOwner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&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;postId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;postId&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authorId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You can only edit your own posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ---------- ROUTES ----------&lt;/span&gt;
&lt;span class="c1"&gt;// Anyone can view (no authentication needed)&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/posts&lt;/span&gt;&lt;span class="dl"&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Authenticated only (any logged-in user)&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;// Create a new post&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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="s1"&gt;Post created&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Authorization: must be admin OR owner&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/posts/:id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isOwner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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="s1"&gt;Post updated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Authorization: admin only&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/posts/:id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isAdmin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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="s1"&gt;Post deleted&lt;/span&gt;&lt;span class="dl"&gt;'&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;See the pattern?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Authentication happens first (isAuthenticated).&lt;/li&gt;
&lt;li&gt;Authorization happens second (isAdmin, isOwner).&lt;/li&gt;
&lt;li&gt;You can be authenticated but not authorized (403).&lt;/li&gt;
&lt;li&gt;You can be neither (401).&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;The Security Checklist&lt;/p&gt;

&lt;p&gt;When building auth, use this mental checklist:&lt;/p&gt;

&lt;p&gt;Question Check&lt;br&gt;
Are passwords hashed with bcrypt (not plain text)? ☐&lt;br&gt;
Is HTTPS enabled everywhere? ☐&lt;br&gt;
Are session cookies marked HttpOnly + Secure? ☐&lt;br&gt;
Do you have rate limiting on login attempts? ☐&lt;br&gt;
Does every protected endpoint check authentication FIRST? ☐&lt;br&gt;
Does every sensitive endpoint check authorization SECOND? ☐&lt;br&gt;
Are you using the correct HTTP status codes (401 vs 403)? ☐&lt;br&gt;
Are JWT tokens (if used) stored in HttpOnly cookies, not localStorage? ☐&lt;/p&gt;



&lt;p&gt;Quick Reference Card (Save This)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│  AUTHENTICATION                     AUTHORIZATION           │
│  "Who are you?"                     "What can you do?"      │
│                                                             │
│  First (login)                      After (every action)    │
│  Passwords, JWT, OAuth              Roles, permissions      │
│  HTTP 401 (Unauthorized)            HTTP 403 (Forbidden)    │
│                                                             │
│  Bouncer at the door 🚪             Bouncer at VIP 🎟️        │
│  "Show ID"                           "Show wristband"       │
└─────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Your Turn&lt;/p&gt;

&lt;p&gt;Go check your own code (or a project you're working on):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do you have endpoints that check authentication?&lt;/li&gt;
&lt;li&gt;Do you have separate checks for authorization?&lt;/li&gt;
&lt;li&gt;Are you using 401 vs 403 correctly?&lt;/li&gt;
&lt;li&gt;Are your tokens in localStorage? (Move them if yes!)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Leave a comment with your biggest "aha moment" or your worst auth-related bug story.&lt;/p&gt;

&lt;p&gt;Follow for more security deep dives (next up: JWT vs. Sessions – which one actually wins?)&lt;/p&gt;




&lt;p&gt;TL;DR: Authentication = proving you're you (login). Authorization = proving you're allowed to do the thing (permissions). One happens first, one happens on every request. Mix them up and hackers win.&lt;/p&gt;

</description>
      <category>authentication</category>
      <category>authorization</category>
      <category>security</category>
      <category>backend</category>
    </item>
    <item>
      <title>The 80/20 Rule for Learning Any New Technology (Stop Reading Manuals)</title>
      <dc:creator>MysticMc</dc:creator>
      <pubDate>Thu, 02 Apr 2026 19:22:45 +0000</pubDate>
      <link>https://dev.to/nazmur96/the-8020-rule-for-learning-any-new-technology-stop-reading-manuals-1fc8</link>
      <guid>https://dev.to/nazmur96/the-8020-rule-for-learning-any-new-technology-stop-reading-manuals-1fc8</guid>
      <description>&lt;p&gt;I've seen the same pattern repeat itself for over a decade.&lt;/p&gt;

&lt;p&gt;A junior engineer gets assigned a new tech stack – let's say React, or Docker, or Rust. Their first instinct?&lt;/p&gt;

&lt;p&gt;They buy a 700-page book. Or start a 40-hour video course.&lt;/p&gt;

&lt;p&gt;Two weeks later, they're burned out, confused, and still can't build anything.&lt;/p&gt;

&lt;p&gt;Meanwhile, a senior engineer picks up the same technology and ships a working feature by Friday.&lt;/p&gt;

&lt;p&gt;The difference isn't intelligence. It's not experience.&lt;/p&gt;

&lt;p&gt;It's the 80/20 Rule.&lt;/p&gt;




&lt;p&gt;What is the 80/20 Rule (Pareto Principle)?&lt;/p&gt;

&lt;p&gt;The Pareto Principle says:&lt;/p&gt;

&lt;p&gt;80% of the results come from 20% of the effort.&lt;/p&gt;

&lt;p&gt;In learning any new technology:&lt;/p&gt;

&lt;p&gt;· 20% of the features will be used 80% of the time.&lt;br&gt;
· 80% of the documentation is edge cases, deprecated methods, or advanced optimizations you will never need as a beginner.&lt;/p&gt;

&lt;p&gt;The trick is: Identify that 20% first. Learn it deeply. Ignore the rest until you actually need it.&lt;/p&gt;



&lt;p&gt;The Wrong Way (What most beginners do)&lt;/p&gt;

&lt;p&gt;Let's say you want to learn Docker.&lt;/p&gt;

&lt;p&gt;❌ The 100% approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the entire Docker documentation (600+ pages).&lt;/li&gt;
&lt;li&gt;Watch a 12-hour "Docker Masterclass" on YouTube.&lt;/li&gt;
&lt;li&gt;Learn about every command: docker network, docker volume, docker swarm, docker stack.&lt;/li&gt;
&lt;li&gt;Try to memorize all flags for docker run.&lt;/li&gt;
&lt;li&gt;Give up when you hit "OverlayFS internals."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;✅ The 80/20 approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Learn 5 commands: docker build, docker run, docker ps, docker stop, docker rm.&lt;/li&gt;
&lt;li&gt;Understand 2 concepts: Dockerfile + image vs. container.&lt;/li&gt;
&lt;li&gt;Build one thing: "Containerize my Node.js app."&lt;/li&gt;
&lt;li&gt;Ship it. Learn docker-compose only when you need two containers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Same technology. 90% less time. Actually shipping code.&lt;/p&gt;



&lt;p&gt;The 80/20 Framework (4 Steps)&lt;/p&gt;

&lt;p&gt;Step 1: Find the "Core 20%" before you write a single line&lt;/p&gt;

&lt;p&gt;Ask three questions:&lt;/p&gt;

&lt;p&gt;Question Example (Learning React)&lt;br&gt;
What do I actually need to build? "A todo app with form inputs and a list"&lt;br&gt;
What 3-5 concepts power 90% of that? Components, props, state, useEffect, JSX&lt;br&gt;
What can I safely ignore for now? Context API, useReducer, memo, custom hooks, SSR&lt;/p&gt;

&lt;p&gt;Research for 20 minutes, not 20 hours. Skim the table of contents. Look at 3 "Getting Started" tutorials. Spot the overlap. That overlap is your 20%.&lt;/p&gt;



&lt;p&gt;Step 2: Build a "Vertical Slice" immediately&lt;/p&gt;

&lt;p&gt;Don't learn horizontally (all about X). Learn vertically (a tiny working thing).&lt;/p&gt;

&lt;p&gt;Horizontal learning (bad):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chapter 1: Variables
Chapter 2: Loops
Chapter 3: Functions
Chapter 4: Objects
... (2 weeks later) ... Chapter 12: Build a project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vertical learning (good):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Write "Hello World" (5 min)
2. Add a button that changes text (10 min)
3. Add an input field (10 min)
4. Add a second page (20 min)
→ You now know 80% of what you need for basic apps.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is not mastery. The goal is a working feedback loop.&lt;/p&gt;




&lt;p&gt;Step 3: Learn just-in-time, not just-in-case&lt;/p&gt;

&lt;p&gt;This is the biggest mindset shift.&lt;/p&gt;

&lt;p&gt;Just-in-case (slow) Just-in-time (fast)&lt;br&gt;
"I should learn all of Git before I start." "I'll learn git commit when I need to save."&lt;br&gt;
"Let me read the entire SQL chapter on JOINs." "I have two tables. Let me Google 'SQL JOIN example'."&lt;br&gt;
"I better understand Kubernetes architecture first." "My Docker container needs to scale. Time to learn pods."&lt;/p&gt;

&lt;p&gt;Your brain remembers what it uses. If you learn it before you need it, you'll forget it. If you learn it because you need it, it sticks.&lt;/p&gt;



&lt;p&gt;Step 4: Stop at "Good Enough" (for now)&lt;/p&gt;

&lt;p&gt;Here's a secret senior devs know:&lt;/p&gt;

&lt;p&gt;You don't need to be an expert. You need to be productive.&lt;/p&gt;

&lt;p&gt;Define your "exit criteria" before you start:&lt;/p&gt;

&lt;p&gt;· ✅ "I can build a CRUD app with authentication."&lt;br&gt;
· ❌ "I understand every internal optimization."&lt;/p&gt;

&lt;p&gt;When you hit your criteria, stop the tutorial. Start building your real project. You will learn the remaining 20% (the hard parts) when you break something in production at 2 AM. That's fine. That's real learning.&lt;/p&gt;



&lt;p&gt;Real Examples: The 20% for Popular Technologies&lt;/p&gt;

&lt;p&gt;Here's the cheat sheet you actually need.&lt;/p&gt;

&lt;p&gt;Git (80% of what you'll ever do)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"message"&lt;/span&gt;
git push origin main
git pull origin main
git clone &amp;lt;url&amp;gt;
git status
git log &lt;span class="nt"&gt;--oneline&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ignore: git rebase, git reflog, git bisect, git filter-branch&lt;/p&gt;




&lt;p&gt;Docker (80% of daily work)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dockerfile basics&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["npm", "start"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commands: docker build, docker run, docker ps, docker stop, docker rm&lt;/p&gt;

&lt;p&gt;Ignore: Docker Swarm, custom networks, volumes (until you need persistence)&lt;/p&gt;




&lt;p&gt;React (80% of components)&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;// 1. Functional component&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;MyComponent&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="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 2. useState for local state&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. useEffect for side effects&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Clicked &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; times`&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. JSX (HTML in JavaScript)&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;clicked&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;times&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&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;Ignore: useReducer, useContext, custom hooks, memo, forwardRef, Portals&lt;/p&gt;




&lt;p&gt;Python (for scripting/data)&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="c1"&gt;# 1. Lists and dicts
&lt;/span&gt;&lt;span class="n"&gt;my_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;my_dict&lt;/span&gt; &lt;span class="o"&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;key&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;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Loops and conditionals
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;my_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 3. Functions
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

&lt;span class="c1"&gt;# 4. List comprehensions
&lt;/span&gt;&lt;span class="n"&gt;squares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="c1"&gt;# 5. Importing libraries
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ignore: Classes (for data work), decorators, async/await, metaclasses&lt;/p&gt;




&lt;p&gt;SQL (for querying)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- 1. SELECT with WHERE&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- 2. JOIN (INNER is 90% of joins)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- 3. GROUP BY with aggregation&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;department&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;salary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;department&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- 4. INSERT/UPDATE/DELETE&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Bob'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Bob'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Bob'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ignore: Window functions, triggers, stored procedures, indexing internals&lt;/p&gt;




&lt;p&gt;Regular Expressions (just enough)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\d      # any digit (0-9)
\w      # any letter/number/underscore
.       # any character
+       # one or more
*       # zero or more
?       # zero or one
{3}     # exactly 3 times
[abc]   # a, b, or c
^       # start of string
$       # end of string
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This covers 90% of regex needs. Ignore lookaheads, lookbehinds, backreferences.&lt;/p&gt;




&lt;p&gt;The Anti-Patterns (What to avoid)&lt;/p&gt;

&lt;p&gt;❌ Tutorial Hell&lt;/p&gt;

&lt;p&gt;You finish 5 courses but can't build a project without hand-holding.&lt;/p&gt;

&lt;p&gt;Fix: Delete the tutorial after 30 minutes. Try to build. Get stuck. Google the error. Repeat.&lt;/p&gt;

&lt;p&gt;❌ The "Perfect Setup" Trap&lt;/p&gt;

&lt;p&gt;You spend 3 days configuring your editor, themes, and dotfiles before writing code.&lt;/p&gt;

&lt;p&gt;Fix: Use the default settings. Write code. Suffer a little. Then customize that one thing that hurts.&lt;/p&gt;

&lt;p&gt;❌ Memorizing Syntax&lt;/p&gt;

&lt;p&gt;You create flashcards for every function.&lt;/p&gt;

&lt;p&gt;Fix: Use Google, ChatGPT, or man pages. Your brain is for patterns, not syntax. I've written Python for 10 years and still Google python list comprehension every single time.&lt;/p&gt;

&lt;p&gt;❌ Reading Release Notes&lt;/p&gt;

&lt;p&gt;You read "What's new in TypeScript 5.3" before you know TypeScript 1.0.&lt;/p&gt;

&lt;p&gt;Fix: Ignore versions until you hit a specific limitation. Use the LTS (Long Term Support) and forget about it.&lt;/p&gt;




&lt;p&gt;The 80/20 Learning Timeline (Example: Learning AWS in 1 week)&lt;/p&gt;

&lt;p&gt;Day 80/20 Focus What you ignore&lt;br&gt;
Day 1 Create an AWS account, launch an EC2 instance (virtual server) IAM roles, VPC networking, pricing calculators&lt;br&gt;
Day 2 SSH into EC2, install Nginx, serve a "Hello World" page Load balancers, auto-scaling, security groups (beyond basics)&lt;br&gt;
Day 3 Store a file in S3 (bucket + upload + public URL) S3 lifecycle policies, cross-region replication, Glacier&lt;br&gt;
Day 4 Set up a database with RDS (Postgres), connect from EC2 Read replicas, performance insights, backup windows&lt;br&gt;
Day 5 Deploy a simple Node.js app that uses S3 + RDS CloudFormation, Terraform, Lambda, API Gateway&lt;/p&gt;

&lt;p&gt;Result: In 5 days, you can build a real app on AWS. You are not an expert. You don't need to be. You are productive.&lt;/p&gt;




&lt;p&gt;The Mindset Shift (Read this twice)&lt;/p&gt;

&lt;p&gt;Here's the truth most tutorials won't tell you:&lt;/p&gt;

&lt;p&gt;You will never "finish" learning a technology.&lt;/p&gt;

&lt;p&gt;There is no graduation day. There is no certificate that makes you an expert.&lt;/p&gt;

&lt;p&gt;The goal is not to know everything.&lt;/p&gt;

&lt;p&gt;The goal is to know enough to build what you want, and learn the rest when you need it.&lt;/p&gt;

&lt;p&gt;The 80/20 rule isn't about being lazy. It's about being strategic. It's about recognizing that:&lt;/p&gt;

&lt;p&gt;· 80% of the documentation is for edge cases you'll never hit.&lt;br&gt;
· 80% of your time will be spent on 20% of the features.&lt;br&gt;
· 80% of your learning should happen while building, not before.&lt;/p&gt;




&lt;p&gt;Your 30-Minute Challenge&lt;/p&gt;

&lt;p&gt;Right now, pick a technology you've been "meaning to learn" but avoiding.&lt;/p&gt;

&lt;p&gt;Set a timer for 30 minutes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Minutes 0-5: Find the official "Quickstart" or "Getting Started" guide. Skim it.&lt;/li&gt;
&lt;li&gt;Minutes 5-10: Identify the 3-5 core commands/concepts. Write them down.&lt;/li&gt;
&lt;li&gt;Minutes 10-25: Build the most minimal thing possible. "Hello World" is fine.&lt;/li&gt;
&lt;li&gt;Minutes 25-30: Break it on purpose. Then fix it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You will know more in 30 minutes than most people learn in 2 weeks of reading.&lt;/p&gt;

&lt;p&gt;Now go break something. 🚀&lt;/p&gt;




&lt;p&gt;TL;DR (The 80/20 of this article)&lt;/p&gt;

&lt;p&gt;· Learn 20% of features that give you 80% of results.&lt;br&gt;
· Build a tiny working thing before you understand it fully.&lt;br&gt;
· Learn just-in-time, not just-in-case.&lt;br&gt;
· Stop at "good enough for now" – mastery comes from real projects.&lt;br&gt;
· Google is your memory. Don't memorize syntax.&lt;/p&gt;




&lt;p&gt;What technology have you been avoiding? Do the 30-minute challenge right now and report back in the comments.&lt;/p&gt;

&lt;p&gt;Follow for more pragmatic, no-fluff learning guides.&lt;/p&gt;

</description>
      <category>learning</category>
      <category>productivity</category>
      <category>mentorship</category>
    </item>
    <item>
      <title>Git vs. GitHub: The 5-minute guide for absolute beginners</title>
      <dc:creator>MysticMc</dc:creator>
      <pubDate>Thu, 02 Apr 2026 19:08:40 +0000</pubDate>
      <link>https://dev.to/nazmur96/git-vs-github-the-5-minute-guide-for-absolute-beginners-32hd</link>
      <guid>https://dev.to/nazmur96/git-vs-github-the-5-minute-guide-for-absolute-beginners-32hd</guid>
      <description>&lt;p&gt;If you're new to coding, you've probably heard people say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Just push it to GitHub."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Make sure you commit your changes."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And if you're like most beginners, you quietly nodded… while secretly thinking: &lt;em&gt;"Wait… are Git and GitHub the same thing?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You are not alone.&lt;/strong&gt; This is &lt;em&gt;the&lt;/em&gt; most common confusion for new developers.&lt;/p&gt;

&lt;p&gt;Here's the truth in one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Git&lt;/strong&gt; is the tool. &lt;strong&gt;GitHub&lt;/strong&gt; is the website where you put the tool's work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let me explain in 5 minutes (or less).&lt;/p&gt;




&lt;h2&gt;
  
  
  The 2-Second Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Git&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;GitHub&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What is it?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A program on your computer&lt;/td&gt;
&lt;td&gt;A website in the cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What does it do?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tracks changes to your code&lt;/td&gt;
&lt;td&gt;Hosts your Git repositories online&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Do you need internet?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ No (works offline)&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Is it free?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes (always)&lt;/td&gt;
&lt;td&gt;✅ Yes for public repos (paid for private team features)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Who makes it?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Linus Torvalds (same guy who made Linux)&lt;/td&gt;
&lt;td&gt;Microsoft (acquired in 2018)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Analogy: Writing a Book 📚
&lt;/h2&gt;

&lt;p&gt;Imagine you're writing a novel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git is your "Save &amp;amp; Track Changes" button (locally)
&lt;/h3&gt;

&lt;p&gt;You write on your laptop. Every time you hit &lt;strong&gt;Save&lt;/strong&gt;, Git takes a snapshot. You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go back to page 37 from yesterday.&lt;/li&gt;
&lt;li&gt;See exactly what words you deleted.&lt;/li&gt;
&lt;li&gt;Try a crazy experiment (kill off the main character!) without losing the original.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;All of this happens on your own computer. No internet required.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub is the library (cloud hosting)
&lt;/h3&gt;

&lt;p&gt;Now you want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Share your book with a co-author.&lt;/li&gt;
&lt;li&gt;Show your progress to the world.&lt;/li&gt;
&lt;li&gt;Make sure your laptop exploding doesn't erase 6 months of work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you &lt;strong&gt;upload&lt;/strong&gt; your Git project to GitHub. It's like putting your book in a public library (or a private locker). Others can read it, suggest changes, or download their own copy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This requires the internet.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Git actually does (the 3 commands you need)
&lt;/h2&gt;

&lt;p&gt;Git tracks your code using &lt;strong&gt;repositories&lt;/strong&gt; (folders with a special &lt;code&gt;.git&lt;/code&gt; hidden folder). Here's the basic workflow:&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;# 1. You change a file (index.html)&lt;/span&gt;
&lt;span class="c"&gt;# 2. You stage it (tell Git "hey, pay attention to this")&lt;/span&gt;
git add index.html

&lt;span class="c"&gt;# 3. You commit it (take a permanent snapshot)&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Fixed the broken button"&lt;/span&gt;

&lt;span class="c"&gt;# 4. Repeat 100 times. You now have 100 snapshots.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Git never touches the internet. It's just a smart "undo" button on steroids.&lt;/p&gt;




&lt;h2&gt;
  
  
  What GitHub actually does (the 3 things you need)
&lt;/h2&gt;

&lt;p&gt;GitHub takes your local Git repository and:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hosts it online&lt;/strong&gt; – So you don't lose it if your computer dies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lets others collaborate&lt;/strong&gt; – Multiple people can push changes to the same repo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adds project management tools&lt;/strong&gt; – Issue tracking, pull requests, code reviews, CI/CD.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# After you've committed locally, you send it to GitHub:&lt;/span&gt;
git push origin main

&lt;span class="c"&gt;# Now your code is on GitHub.com. Anyone can see it (if public).&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Common beginner mistakes (and how to fix them)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ❌ "I deleted my project folder. But I pushed to GitHub yesterday, so I'm safe!"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Not exactly.&lt;/strong&gt; GitHub has your code, but you still need to &lt;code&gt;git clone&lt;/code&gt; it back down. It's not magic. You have to explicitly download it.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Fix:&lt;/strong&gt; &lt;code&gt;git clone https://github.com/yourname/yourrepo.git&lt;/code&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  ❌ "I made changes on GitHub.com (edited a file in the browser) and now my local version is different."
&lt;/h3&gt;

&lt;p&gt;This creates a &lt;strong&gt;merge conflict&lt;/strong&gt;. GitHub has version A, your laptop has version B. Git doesn't know which is correct.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Fix:&lt;/strong&gt; &lt;code&gt;git pull origin main&lt;/code&gt; before you start working. Always.&lt;/p&gt;




&lt;h3&gt;
  
  
  ❌ "Git and GitHub are the same company, right?"
&lt;/h3&gt;

&lt;p&gt;Nope. Git was created in 2005 by Linus Torvalds (Linux guy). GitHub launched in 2008 as a startup that built a website &lt;em&gt;around&lt;/em&gt; Git. Microsoft bought GitHub for $7.5 billion in 2018.&lt;/p&gt;

&lt;p&gt;Alternatives to GitHub:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitLab&lt;/strong&gt; (self-hosted option)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bitbucket&lt;/strong&gt; (Atlassian)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SourceForge&lt;/strong&gt; (the old guard)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But they all use &lt;strong&gt;Git&lt;/strong&gt; underneath. Git is the engine. GitHub is just one car brand.&lt;/p&gt;




&lt;h2&gt;
  
  
  Visual timeline of a real workflow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Your laptop]                    [GitHub cloud]
     |                                |
     | 1. Write code                  |
     | 2. git add .                   |
     | 3. git commit -m "..."         |
     |                                |
     | 4. git push -----------------&amp;gt; | 5. Code appears online
     |                                |
     | 6. Friend clones or forks      |
     |    the repo from GitHub         |
     |                                |
     | 7. git pull &amp;lt;------------------| 8. Get their changes
     |                                |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Internet is only needed for steps 4, 6, and 8.&lt;/strong&gt; Everything else works offline.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why you need BOTH (not one or the other)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If you ONLY use Git (no GitHub)&lt;/th&gt;
&lt;th&gt;If you ONLY use GitHub (no Git locally)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;✅ You have version history&lt;/td&gt;
&lt;td&gt;✅ Your code is backed up online&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;✅ You can undo mistakes&lt;/td&gt;
&lt;td&gt;✅ Others can see your work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;❌ No cloud backup&lt;/td&gt;
&lt;td&gt;❌ You need internet for &lt;em&gt;everything&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;❌ Hard to collaborate&lt;/td&gt;
&lt;td&gt;❌ You can't work offline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;❌ Computer dies = code dies&lt;/td&gt;
&lt;td&gt;❌ You're just editing files in a browser (not real dev work)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The pro move:&lt;/strong&gt; Use Git locally for daily work. Push to GitHub periodically for backup and collaboration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your first 10 minutes with Git + GitHub
&lt;/h2&gt;

&lt;p&gt;Here's a concrete starter plan:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Install Git
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mac:&lt;/strong&gt; &lt;code&gt;brew install git&lt;/code&gt; or download from git-scm.com&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows:&lt;/strong&gt; Download from git-scm.com (Git Bash included)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linux:&lt;/strong&gt; &lt;code&gt;sudo apt install git&lt;/code&gt; (Ubuntu/Debian)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Configure your identity (one time)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; user.name &lt;span class="s2"&gt;"Your Name"&lt;/span&gt;
git config &lt;span class="nt"&gt;--global&lt;/span&gt; user.email &lt;span class="s2"&gt;"your@email.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Create a GitHub account
&lt;/h3&gt;

&lt;p&gt;Go to &lt;a href="https://github.com" rel="noopener noreferrer"&gt;github.com&lt;/a&gt; and sign up (free).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Create a repo on GitHub
&lt;/h3&gt;

&lt;p&gt;Click the &lt;strong&gt;"+"&lt;/strong&gt; icon → "New repository" → name it "my-first-repo" → check "Add a README" → Create.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Clone it to your computer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/yourusername/my-first-repo.git
&lt;span class="nb"&gt;cd &lt;/span&gt;my-first-repo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Make a change
&lt;/h3&gt;

&lt;p&gt;Open the README.md file. Add a line: &lt;code&gt;"I understand Git vs. GitHub now!"&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7: Commit and push
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add README.md
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Added my realization"&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 8: Refresh GitHub.com
&lt;/h3&gt;

&lt;p&gt;See your change live. 🎉&lt;/p&gt;




&lt;h2&gt;
  
  
  The "Aha!" moment explained
&lt;/h2&gt;

&lt;p&gt;Here's how you'll know you truly understand:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Git&lt;/strong&gt; is to &lt;strong&gt;GitHub&lt;/strong&gt; as &lt;strong&gt;your camera roll&lt;/strong&gt; is to &lt;strong&gt;Instagram&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Your camera roll (Git) stores all your photos locally. You can edit, delete, and organize them offline.&lt;/li&gt;
&lt;li&gt;Instagram (GitHub) is where you upload some of those photos to share with the world.&lt;/li&gt;
&lt;li&gt;You can delete Instagram and still have your photos. You can delete your camera roll and lose everything (unless Instagram had a copy).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Never trust the cloud as your only copy.&lt;/strong&gt; Always have Git locally.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ (from actual beginners)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  "Do I need to learn Git before using GitHub?"
&lt;/h3&gt;

&lt;p&gt;Yes, but only the basics (&lt;code&gt;add&lt;/code&gt;, &lt;code&gt;commit&lt;/code&gt;, &lt;code&gt;push&lt;/code&gt;, &lt;code&gt;pull&lt;/code&gt;, &lt;code&gt;clone&lt;/code&gt;). You can learn those in 30 minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Can I use GitHub without using Git?"
&lt;/h3&gt;

&lt;p&gt;Technically yes (you can edit files directly on GitHub.com). But that's like editing a website using only Notepad. Professionals don't do this.&lt;/p&gt;

&lt;h3&gt;
  
  
  "What's a 'fork'?"
&lt;/h3&gt;

&lt;p&gt;A copy of someone else's GitHub repository that lives under &lt;em&gt;your&lt;/em&gt; GitHub account. You can modify it without affecting the original.&lt;/p&gt;

&lt;h3&gt;
  
  
  "What's a 'pull request'?"
&lt;/h3&gt;

&lt;p&gt;You saying: "Hey, I changed your code. Here are my changes. Want to pull them into your project?"&lt;/p&gt;

&lt;h3&gt;
  
  
  "Is GitHub safe for proprietary code?"
&lt;/h3&gt;

&lt;p&gt;Yes, but you need a &lt;strong&gt;private repository&lt;/strong&gt; (paid on some plans, free on GitHub for small teams). For top-secret stuff, companies use self-hosted GitLab.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final cheat sheet (bookmark this)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Git commands (work offline)&lt;/span&gt;
git init                 &lt;span class="c"&gt;# Start tracking a folder&lt;/span&gt;
git add file.txt         &lt;span class="c"&gt;# Stage a file&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"message"&lt;/span&gt;  &lt;span class="c"&gt;# Take a snapshot&lt;/span&gt;
git log                  &lt;span class="c"&gt;# See your history&lt;/span&gt;
git diff                 &lt;span class="c"&gt;# See what changed&lt;/span&gt;
git checkout &lt;span class="o"&gt;[&lt;/span&gt;commitID]  &lt;span class="c"&gt;# Go back in time&lt;/span&gt;

&lt;span class="c"&gt;# GitHub commands (need internet)&lt;/span&gt;
git clone &lt;span class="o"&gt;[&lt;/span&gt;url]          &lt;span class="c"&gt;# Download a repo from GitHub&lt;/span&gt;
git push origin main     &lt;span class="c"&gt;# Upload your commits to GitHub&lt;/span&gt;
git pull origin main     &lt;span class="c"&gt;# Download latest changes from GitHub&lt;/span&gt;
git remote &lt;span class="nt"&gt;-v&lt;/span&gt;            &lt;span class="c"&gt;# See which GitHub URL you're connected to&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Your turn
&lt;/h2&gt;

&lt;p&gt;Now go do this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your terminal.&lt;/li&gt;
&lt;li&gt;Create a folder called &lt;code&gt;git-practice&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;git init&lt;/code&gt; inside it.&lt;/li&gt;
&lt;li&gt;Create a file called &lt;code&gt;hello.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;git add .&lt;/code&gt; then &lt;code&gt;git commit -m "first commit"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Go to GitHub, create a new repo (don't add a README this time).&lt;/li&gt;
&lt;li&gt;Follow the "...or push an existing repository from the command line" instructions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You just became a real developer. 🚀&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Found this helpful?&lt;/strong&gt; Leave a comment with your biggest Git or GitHub facepalm moment. We've all been there.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want more beginner guides? Drop a follow and I'll cover branching, merging, and fixing merge conflicts next.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>versioncontrol</category>
      <category>github</category>
    </item>
    <item>
      <title>🚀 Beginner’s Guide: TCP vs. UDP – Which One Should You Use?</title>
      <dc:creator>MysticMc</dc:creator>
      <pubDate>Thu, 02 Apr 2026 18:46:23 +0000</pubDate>
      <link>https://dev.to/nazmur96/beginners-guide-tcp-vs-udp-which-one-should-you-use-4nin</link>
      <guid>https://dev.to/nazmur96/beginners-guide-tcp-vs-udp-which-one-should-you-use-4nin</guid>
      <description>&lt;p&gt;If you’ve ever looked at a networking diagram or tried to debug a slow API, you’ve probably seen the acronyms &lt;strong&gt;TCP&lt;/strong&gt; and &lt;strong&gt;UDP&lt;/strong&gt;. They sound similar, but they are fundamentally different.&lt;/p&gt;

&lt;p&gt;Think of them as two different ways to send a letter (or a package) across the internet.&lt;/p&gt;

&lt;p&gt;In this beginner-friendly guide, we’ll break down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What TCP and UDP actually are.&lt;/li&gt;
&lt;li&gt;The "Mail Analogy" (visual thinking).&lt;/li&gt;
&lt;li&gt;Key differences in speed vs. reliability.&lt;/li&gt;
&lt;li&gt;When to use which in your projects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s dive in! 🏊‍♂️&lt;/p&gt;




&lt;h2&gt;
  
  
  The OSI Model (The 2-second intro)
&lt;/h2&gt;

&lt;p&gt;Without getting too theoretical, the internet works in layers. TCP and UDP live at the &lt;strong&gt;Transport Layer&lt;/strong&gt; (Layer 4). Their job is simple: &lt;strong&gt;Take data from your app, package it up, and send it to another computer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But &lt;em&gt;how&lt;/em&gt; they package it is where the magic happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Analogy: Mailing a Book 📖
&lt;/h2&gt;

&lt;p&gt;Imagine you want to send a 1,000-page book to a friend.&lt;/p&gt;

&lt;h3&gt;
  
  
  TCP = Registered Mail (With tracking &amp;amp; receipts)
&lt;/h3&gt;

&lt;p&gt;You go to the post office. You tear the book into 1,000 individual pages. You number each page.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Send:&lt;/strong&gt; You mail page 1. Your friend texts you: "Got page 1."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Send:&lt;/strong&gt; You mail page 2. Your friend texts: "Got page 2."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Oh no!&lt;/strong&gt; Page 5 gets lost. Your friend texts: "I’m missing page 5." You re-send page 5.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; Your friend receives every single page, in perfect order, before reading the book.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  UDP = Standard Mail (Fire &amp;amp; forget)
&lt;/h3&gt;

&lt;p&gt;You rip out the 1,000 pages, shove them into envelopes without numbering them, and dump them in the mailbox.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Send:&lt;/strong&gt; You don't care if they arrive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Send:&lt;/strong&gt; You don't care what order they arrive in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; Your friend gets 980 pages, out of order, 10 seconds faster. If a page is missing, he just reads around it.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Technical Definitions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  TCP (Transmission Control Protocol)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nickname:&lt;/strong&gt; "The Reliable One"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handshake:&lt;/strong&gt; Requires a 3-way handshake (SYN, SYN-ACK, ACK) before sending data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error checking:&lt;/strong&gt; Yes. If data is corrupted or lost, TCP asks for it again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ordering:&lt;/strong&gt; Yes. Packets are reassembled in the exact order sent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed:&lt;/strong&gt; Slower (due to overhead).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  UDP (User Datagram Protocol)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nickname:&lt;/strong&gt; "The Fast One"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handshake:&lt;/strong&gt; No handshake. Just sends data immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error checking:&lt;/strong&gt; Minimal (checksum only). No retransmission.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ordering:&lt;/strong&gt; No. Packets are processed as they arrive (or not at all).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed:&lt;/strong&gt; Very fast.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Code Snippet (Python example)
&lt;/h2&gt;

&lt;p&gt;Here is a simple way to visualize the difference in code:&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="c1"&gt;# TCP: You wait for confirmation
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;
&lt;span class="n"&gt;tcp_socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tcp_socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;lt;-- Blocks until connection is made
&lt;/span&gt;&lt;span class="n"&gt;tcp_socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET / HTTP/1.1&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tcp_socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;lt;-- Waits for guaranteed data
&lt;/span&gt;
&lt;span class="c1"&gt;# UDP: Just throw it into the void
&lt;/span&gt;&lt;span class="n"&gt;udp_socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_DGRAM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;udp_socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello world&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;192.168.1.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# No connection. No confirmation. Just vibes.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Head-to-Head Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;TCP&lt;/th&gt;
&lt;th&gt;UDP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Connection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Connection-oriented&lt;/td&gt;
&lt;td&gt;Connectionless&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reliability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ High (Retransmits lost data)&lt;/td&gt;
&lt;td&gt;❌ Low (No retransmission)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ordering&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Preserves order&lt;/td&gt;
&lt;td&gt;❌ No order guarantee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;🐢 Slower&lt;/td&gt;
&lt;td&gt;🚀 Faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Header Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;20-60 bytes&lt;/td&gt;
&lt;td&gt;8 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Congestion Control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (slows down if network is busy)&lt;/td&gt;
&lt;td&gt;No (keeps blasting)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Broadcast/Multicast&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Real-World Examples (When to use which)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use TCP when:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Browsing the web (HTTP/HTTPS).&lt;/strong&gt; You cannot have missing chunks of a webpage.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Sending an email (SMTP, IMAP).&lt;/strong&gt; Missing a sentence changes the meaning.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Transferring a file (FTP, SFTP).&lt;/strong&gt; One wrong byte corrupts a ZIP file.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Connecting to a database (PostgreSQL, MySQL).&lt;/strong&gt; You need accurate queries.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Use UDP when:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Live Video Streaming (YouTube Live, Zoom, Twitch).&lt;/strong&gt; You’d rather have a slightly blurry frame than wait for a missing packet to retransmit (which would cause buffering).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Online Gaming (Call of Duty, Fortnite).&lt;/strong&gt; If you lose one packet about player position, don't ask for it again—just update the next position. Speed &amp;gt; Perfect accuracy.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;DNS Lookups.&lt;/strong&gt; Translating &lt;code&gt;google.com&lt;/code&gt; to an IP address requires one tiny request and reply. UDP is perfect because it's fast and connectionless.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;VoIP (Phone calls).&lt;/strong&gt; "Can you hear me now?" is better than "Waiting for packet #42..."&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pro Tip:&lt;/strong&gt; Even Zoom and YouTube actually use &lt;strong&gt;TCP&lt;/strong&gt; for the &lt;em&gt;control signals&lt;/em&gt; (login, chat) but &lt;strong&gt;UDP&lt;/strong&gt; for the actual video/audio stream. You can use both!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The "Gotcha" for Beginners
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"If UDP is faster, why don't we always use it?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because &lt;strong&gt;reliability matters more than speed for most things&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine buying something on Amazon using UDP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You click "Buy." The packet gets lost.&lt;/li&gt;
&lt;li&gt;Your credit card gets charged (packet arrived).&lt;/li&gt;
&lt;li&gt;The confirmation email never arrives (packet lost).&lt;/li&gt;
&lt;li&gt;You have no idea if the order worked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With TCP, the browser keeps asking, "Did you get my request?" until it gets a "Yes."&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary Cheat Sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If you need...&lt;/th&gt;
&lt;th&gt;Choose...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100% accuracy&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;TCP&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resending lost data&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;TCP&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ordered packets&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;TCP&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speed above all else&lt;/td&gt;
&lt;td&gt;🚀 &lt;strong&gt;UDP&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live streaming / Gaming&lt;/td&gt;
&lt;td&gt;🚀 &lt;strong&gt;UDP&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Broadcasting to many devices&lt;/td&gt;
&lt;td&gt;🚀 &lt;strong&gt;UDP&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TCP is polite:&lt;/strong&gt; "Hello, are you there? Okay, I will send page 1. Did you get it? Okay, here is page 2..."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UDP is reckless:&lt;/strong&gt; "HERE IS PAGE 1, PAGE 2, PAGE 5, PAGE 3... GOOD LUCK!"&lt;/p&gt;

&lt;p&gt;Now that you know the difference, go look at your favorite app. Is it using TCP or UDP? (Hint: &lt;code&gt;netstat -an&lt;/code&gt; on your terminal can tell you!).&lt;/p&gt;

&lt;p&gt;Happy coding! 🖥️&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Found this helpful? Leave a like or comment below. What protocol confused you the most when you started?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>networking</category>
      <category>tcp</category>
      <category>udp</category>
    </item>
    <item>
      <title>Struggling to break into Devops!</title>
      <dc:creator>MysticMc</dc:creator>
      <pubDate>Wed, 01 Apr 2026 13:58:38 +0000</pubDate>
      <link>https://dev.to/nazmur96/struggling-to-break-into-devops-3dog</link>
      <guid>https://dev.to/nazmur96/struggling-to-break-into-devops-3dog</guid>
      <description>&lt;p&gt;Most engineers don’t struggle because they’re undisciplined.&lt;br&gt;
They struggle because the way DevOps is taught mirrors the way a library is organized: everything is available, but nothing is connected. You finish a Docker course and move to Kubernetes. You finish Kubernetes and start Terraform. Each thing makes sense on its own. None of it prepares you for the moment someone puts an incident in front of you and asks what you see.&lt;/p&gt;

&lt;p&gt;That gap, between knowing tools and thinking in systems , is where most careers stall. And almost no one tells you it exists until you’re already stuck in it.&lt;/p&gt;

&lt;p&gt;Everything on this page exists to close that gap deliberately.&lt;/p&gt;

&lt;p&gt;Start Here&lt;br&gt;
The DevOps Operating System&lt;br&gt;
The structured path from confusion to a hireable engineer&lt;/p&gt;

&lt;p&gt;It’s a 6-week curriculum built around one progression: reading → building → understanding → explaining → getting hired. The first four phases give you a working local environment, Linux, Git, Docker, and Kubernetes running on MicroK8s. After that, the curriculum moves you into realistic production scenarios: real world tickets, infrastructure tasks, and a capstone enterprise system you build end-to-end.&lt;/p&gt;

&lt;p&gt;By the end, you won’t need someone to walk you through a debugging session. You’ll already know what to look for, and how to explain what you found.&lt;/p&gt;

&lt;p&gt;If You’re Not Ready for That Yet&lt;br&gt;
Start with these. They’re free. They’re the same resources engineers have used to get clear, get confident, and get interviews.&lt;/p&gt;

&lt;p&gt;Free DevOps Resume Template. DevOps resumes fail because they list tools instead of demonstrating judgment. This template is structured around what hiring managers actually look for: evidence that you understand real infrastructure and can demonstrate operational judgment under real constraints, not just coursework.&lt;/p&gt;

&lt;p&gt;Real-World DevOps Project Portfolio The question that ends most interviews isn’t technical. It’s “Can you show me something you built and debugged?” This portfolio gives you production-style projects across CI/CD, Kubernetes, and cloud infrastructure, the kind that answer that question before it’s asked.&lt;/p&gt;

&lt;p&gt;GitHub Troubleshooting Toolkit: When something breaks in production, the engineers who stay calm aren’t calmer by nature. They have a method. This toolkit gives you a repeatable diagnostic process so that “the system is down” becomes a starting point, not a crisis.&lt;/p&gt;

&lt;p&gt;For Engineers Ready to Go Deeper&lt;br&gt;
The Kubernetes Detective: A pod is crashing. kubectl logs shows nothing useful. Three engineers are watching you, and you have no idea where to start. This guide is the systematic debugging method that turns that moment from panic into a process. It's what separates engineers who guess from engineers who diagnose.&lt;/p&gt;

&lt;p&gt;The DevOps Interview Decoder. Candidates fail interviews not because of a lack of knowledge, but because they can’t articulate how systems actually work under pressure. This framework closes that gap. Engineers who have used it have landed roles exceeding $170K globally, not because it’s a script, but because it teaches you to think out loud like someone who has already seen production.&lt;/p&gt;

&lt;p&gt;AI for DevOps: AI won’t replace engineers. It will replace engineers who treat it as a search engine. This guide covers 47 tested prompts for troubleshooting, automating learning, and recovering hours every week, built for engineers who want to operate faster, not just differently.&lt;/p&gt;

&lt;p&gt;Stay Close&lt;br&gt;
Weekly Newsletter Every week: a breakdown of a real production incident, an architecture lesson, a cost-saving tactic used inside actual companies, and occasionally an honest account of something that went badly and what it taught. No hype. No listicles. Signal only.&lt;br&gt;
Sign Up Here&lt;/p&gt;

&lt;p&gt;You don’t need more courses.&lt;/p&gt;

&lt;p&gt;You need fewer distractions, better systems, and one structured path that connects what you’re learning to what production actually looks like.&lt;/p&gt;

&lt;p&gt;🔖 Tip: Bookmark this page. I update it regularly with new tools, labs, and frameworks.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
