<?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>Forem: Naresh Lohar</title>
    <description>The latest articles on Forem by Naresh Lohar (@codephoenix86).</description>
    <link>https://forem.com/codephoenix86</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1470437%2F23da8a62-81d0-48f8-9f98-25bc8e4f878b.jpeg</url>
      <title>Forem: Naresh Lohar</title>
      <link>https://forem.com/codephoenix86</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/codephoenix86"/>
    <language>en</language>
    <item>
      <title>The Evolution of Backend and DevOps: A 25-Year Prediction Timeline</title>
      <dc:creator>Naresh Lohar</dc:creator>
      <pubDate>Sat, 11 Apr 2026 09:41:02 +0000</pubDate>
      <link>https://forem.com/codephoenix86/the-evolution-of-backend-and-devops-a-25-year-prediction-timeline-d8m</link>
      <guid>https://forem.com/codephoenix86/the-evolution-of-backend-and-devops-a-25-year-prediction-timeline-d8m</guid>
      <description>&lt;p&gt;The software engineering landscape is shifting incredibly fast. With developers completing tasks up to &lt;a href="https://arxiv.org/pdf/2302.06590" rel="noopener noreferrer"&gt;55% faster&lt;/a&gt; using AI tools, many of us are wondering what the future actually holds for our careers.&lt;/p&gt;

&lt;p&gt;Based on current labor market data, economic forecasts, and technology roadmaps, here is a realistic, data-driven timeline of how Backend and DevOps engineering roles will evolve over the next 25 years.&lt;/p&gt;

&lt;p&gt;Part 1: The Next 5 to 10 Years (Confidence: 80% to 90%)&lt;br&gt;
Why the high confidence? These predictions are backed by hard data unfolding right now. We are already seeing a &lt;a href="https://digitaleconomy.stanford.edu/wp-content/uploads/2025/08/Canaries_BrynjolfssonChandarChen.pdf" rel="noopener noreferrer"&gt;13% relative decline&lt;/a&gt; in hiring for early-career AI-exposed roles, while demand for senior talent remains strong.&lt;/p&gt;

&lt;p&gt;The Rise of the "Curator" (Years 1–5)&lt;br&gt;
Despite fears of AI replacing developers, the software development market is &lt;a href="https://www.morganstanley.com/insights/articles/ai-software-development-industry-growth" rel="noopener noreferrer"&gt;projected to reach $61 billion by 2029&lt;/a&gt;. However, the day-to-day work is changing. Up to 30% of current routine engineering tasks will be automated. Because AI-generated code will create massive bottlenecks in testing and security, backend engineers will shift from writing raw code to acting as curators, reviewers, and integrators.&lt;/p&gt;

&lt;p&gt;Software 3.0 and AIOps (Years 5–10)&lt;br&gt;
We will soon enter the era of "Software 3.0," where AI agents operate autonomously within the development lifecycle to reason, plan, and execute across domains. For DevOps, the role will not disappear, but it will aggressively pivot into AIOps and MLOps. Engineers will transition away from writing manual deployment pipelines to managing self-healing systems and optimizing massive cloud infrastructure for AI models.&lt;/p&gt;

&lt;p&gt;Part 2: 15 to 25 Years Out (Confidence: 40% to 50%)&lt;br&gt;
Why the lower confidence? Looking decades into the future relies on speculative roadmaps and assumes no major regulatory roadblocks. The direction is clear, but the exact timing is highly unpredictable.&lt;/p&gt;

&lt;p&gt;The Human Development Lifecycle (15 Years Out)&lt;br&gt;
Eventually, the traditional Software Development Lifecycle (SDLC) will transition into the Human Development Lifecycle (HDLC). Natural human language will become the ultimate abstraction layer for programming. The value of a backend engineer will no longer be syntax knowledge, but rather the ability to design complex prompt frameworks, ensure pristine data quality, and handle the extreme edge cases that AI compilers miss.&lt;/p&gt;

&lt;p&gt;The Era of Physical AI (20 Years Out)&lt;br&gt;
As predicted by tech leaders like Nvidia CEO Jensen Huang, the frontier of innovation will move away from digital screens and toward "Physical AI". AI systems will learn to understand real-world physics, mechanics, and spatial reasoning to power advanced robotics. DevOps will likely transition into physical fleet management, ensuring absolute reliability and ultra-low latency between cloud models and physical machines operating in the real world.&lt;/p&gt;

&lt;p&gt;System Stewardship (25 Years Out)&lt;br&gt;
By the 2050s, the strict definitions of "Frontend," "Backend," and "DevOps" will likely be obsolete. However, academic research and expert surveys suggest that AI will not entirely replace human engineers. Instead, engineers will elevate to the role of "system stewards". Humans will be required to maintain, train, moderate, and lead highly advanced AI systems, applying human creativity and ethical judgment to direct machine execution at a massive scale.&lt;/p&gt;

&lt;p&gt;The Takeaway&lt;br&gt;
The keyboard might become less central to our daily jobs, but human judgment, system design, and strategic thinking will only become more valuable. The best way to future-proof your career is to stop competing with AI on code generation and start mastering how to orchestrate it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>backend</category>
      <category>career</category>
      <category>devops</category>
    </item>
    <item>
      <title>How I Deployed My First Production App on AWS EC2 — Every Mistake I Made</title>
      <dc:creator>Naresh Lohar</dc:creator>
      <pubDate>Sat, 21 Mar 2026 12:26:50 +0000</pubDate>
      <link>https://forem.com/codephoenix86/how-i-deployed-my-first-production-app-on-aws-ec2-every-mistake-i-made-4e8e</link>
      <guid>https://forem.com/codephoenix86/how-i-deployed-my-first-production-app-on-aws-ec2-every-mistake-i-made-4e8e</guid>
      <description>&lt;p&gt;I am a third-year computer science student at IIIT Sonepat. Recently, I deployed my chat application, FastChat, on a live AWS EC2 server with HTTPS support, a domain name, and a proper Nginx reverse proxy. This blog is going to be a description of exactly what I did, how it all fits together, and what I did wrong so you don’t have to.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;FastChat is a REST + WebSocket chat API built with:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;App:&lt;/strong&gt; Node.js, Express.js, Socket.io, MongoDB, PostgreSQL, Redis, AWS S3 (avatar storage), Jest + Supertest (testing)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure:&lt;/strong&gt; Docker, Docker Compose, Nginx, AWS EC2, Let's Encrypt (SSL), DuckDNS (free domain)&lt;/p&gt;

&lt;p&gt;The live API is running at &lt;strong&gt;&lt;a href="https://fastchat.duckdns.org" rel="noopener noreferrer"&gt;https://fastchat.duckdns.org&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This URL may not always be live as I shut down the EC2 instance when not in use to avoid AWS charges. If you want to see the code instead, check out the GitHub repo at &lt;a href="https://github.com/codephoenix86" rel="noopener noreferrer"&gt;codephoenix86&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;Before jumping into steps, here's how everything connects:&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.amazonaws.com%2Fuploads%2Farticles%2Fmagye98gp1gukl2nnqzd.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.amazonaws.com%2Fuploads%2Farticles%2Fmagye98gp1gukl2nnqzd.png" alt=" " width="800" height="691"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key security decision: &lt;strong&gt;only ports 22 (SSH), 80 (HTTP), and 443 (HTTPS) are exposed to the internet.&lt;/strong&gt; The databases and the app itself on port 3000 are completely internal — no one can reach them directly. All traffic must go through Nginx.&lt;/p&gt;

&lt;p&gt;I used two Docker networks to enforce this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;fastchat network&lt;/strong&gt; — connects FastChat app to its databases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;shared gateway network&lt;/strong&gt; — connects FastChat app to Nginx only&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step-by-Step: How I Deployed It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1 — Build and Push Docker Image
&lt;/h3&gt;

&lt;p&gt;First, I built the Docker image of my chat app locally and pushed it to DockerHub:&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; nareshlohar86/fastchat &lt;span class="nb"&gt;.&lt;/span&gt;
docker push nareshlohar86/fastchat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Fey7b0ciphrud4xr69chl.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.amazonaws.com%2Fuploads%2Farticles%2Fey7b0ciphrud4xr69chl.png" alt=" " width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now the image is available anywhere, including my EC2 server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Write the Docker Compose Files
&lt;/h3&gt;

&lt;p&gt;I split my setup into &lt;strong&gt;two repositories&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;fastchat&lt;/strong&gt; — the app itself (Node.js + 3 databases)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;infra&lt;/strong&gt; — Nginx reverse proxy + Certbot for SSL&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;fastchat/compose.yml&lt;/strong&gt; runs 4 services — fastchat, mongodb, postgres, redis — all connected via &lt;code&gt;fastchat&lt;/code&gt; network. FastChat is also connected to &lt;code&gt;shared-gateway&lt;/code&gt; so Nginx can reach it. Databases have no external port exposure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;infra/compose.yml&lt;/strong&gt; runs 2 services — nginx and certbot — both connected to &lt;code&gt;shared-gateway&lt;/code&gt;. Nginx is the only container with ports 80 and 443 exposed to the host.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — Launch an EC2 Instance
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2Fkntjq49cx4ty03slzyia.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.amazonaws.com%2Fuploads%2Farticles%2Fkntjq49cx4ty03slzyia.png" alt=" " width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I created a free AWS account, launched a &lt;strong&gt;t3.micro Ubuntu&lt;/strong&gt; instance, and downloaded the &lt;code&gt;.pem&lt;/code&gt; key pair for SSH access.&lt;/p&gt;

&lt;p&gt;In the Security Group, I opened exactly 3 ports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Port 22 — SSH&lt;/li&gt;
&lt;li&gt;Port 80 — HTTP&lt;/li&gt;
&lt;li&gt;Port 443 — HTTPS&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2Fe6p1lspywonx1v1ccbza.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.amazonaws.com%2Fuploads%2Farticles%2Fe6p1lspywonx1v1ccbza.png" alt=" " width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're new to AWS, here's the official guide: &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-creating.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-creating.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 4 — Set Up the Server
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2Fovxy394k1e1hck10l93i.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.amazonaws.com%2Fuploads%2Farticles%2Fovxy394k1e1hck10l93i.png" alt=" " width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SSH into the instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/my-aws-key.pem ubuntu@&amp;lt;your-ec2-public-ip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then install the tools needed:&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;# Install Docker (simplest method)&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://get.docker.com | sh
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
newgrp docker

&lt;span class="c"&gt;# Install Git&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;git &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="c"&gt;# Install NVM + Node.js (LTS)&lt;/span&gt;
curl &lt;span class="nt"&gt;-o-&lt;/span&gt; https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc
nvm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--lts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then clone both repositories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:yourusername/fastchat.git
git clone git@github.com:yourusername/infra.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;To authenticate with GitHub via SSH, generate a key pair on EC2 (&lt;code&gt;ssh-keygen&lt;/code&gt;) and add the public key to your GitHub account settings.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 5 — Set Up a Free Domain with DuckDNS
&lt;/h3&gt;

&lt;p&gt;To get HTTPS, you need a domain name. I used &lt;strong&gt;DuckDNS&lt;/strong&gt; — it's completely free.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://www.duckdns.org" rel="noopener noreferrer"&gt;https://www.duckdns.org&lt;/a&gt; and log in&lt;/li&gt;
&lt;li&gt;Claim a subdomain (e.g., &lt;code&gt;fastchat.duckdns.org&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Point it to your EC2 public IP address&lt;/li&gt;
&lt;/ol&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.amazonaws.com%2Fuploads%2Farticles%2Facr8m5nrwp5nepyi3qxa.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.amazonaws.com%2Fuploads%2Farticles%2Facr8m5nrwp5nepyi3qxa.png" alt=" " width="800" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Done. Your app now has a real domain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6 — Configure Nginx with Bind Mounts
&lt;/h3&gt;

&lt;p&gt;Since Nginx runs inside a Docker container, its filesystem is isolated. To let Nginx read my config files and SSL certificates, I used &lt;strong&gt;bind mounts&lt;/strong&gt; — they link a folder on the host machine to a path inside the 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="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx/nginx.conf:/etc/nginx/nginx.conf:ro&lt;/span&gt;      &lt;span class="c1"&gt;# config (read-only)&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./certbot/certs:/etc/letsencrypt&lt;/span&gt;                  &lt;span class="c1"&gt;# SSL certificates&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./certbot/www:/var/www/certbot&lt;/span&gt;                    &lt;span class="c1"&gt;# for domain verification&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My initial Nginx config listens on port 80 and serves the Let's Encrypt verification path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;fastchat.duckdns.org&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/.well-known/acme-challenge/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/var/www/certbot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="nv"&gt;$host$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;# redirect everything else to HTTPS&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;Both Nginx and Certbot share the same volumes:&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;certbot&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;certbot/certbot&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./certbot/certs:/etc/letsencrypt&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./certbot/www:/var/www/certbot&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the key to how domain verification works without downtime:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;./certbot/www:/var/www/certbot&lt;/code&gt; — Certbot writes the secret verification token here. Nginx serves it when Let's Encrypt sends a request to &lt;code&gt;/.well-known/acme-challenge/&lt;/code&gt;. Both containers read/write the same folder on the host machine.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;./certbot/certs:/etc/letsencrypt&lt;/code&gt; — Once the certificate is issued, Certbot stores it here. Nginx reads the certificate from this same folder to enable HTTPS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This shared volume approach means Nginx and Certbot never need to talk to each other directly — they just read and write to the same folders on the host machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7 — Get a Free SSL Certificate with Certbot
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Important: Start Nginx first, then run Certbot.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;(I learned this the hard way — see Challenges below.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; nginx   &lt;span class="c"&gt;# inside infra/ folder&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then issue the certificate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; certbot certonly &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--webroot&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--webroot-path&lt;/span&gt; /var/www/certbot &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--email&lt;/span&gt; your-email@example.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--agree-tos&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--no-eff-email&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; fastchat.duckdns.org
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How this works:&lt;/strong&gt; Let's Encrypt sends an HTTP request to your domain asking for a secret token. Certbot writes that token to &lt;code&gt;/var/www/certbot&lt;/code&gt;, and since Nginx is already serving that path, Let's Encrypt can read it and verify you own the domain. No downtime, no stopping your server.&lt;/p&gt;

&lt;p&gt;After this succeeds, Certbot stores your certificate at &lt;code&gt;/etc/letsencrypt/live/fastchat.duckdns.org/&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8 — Enable HTTPS in Nginx
&lt;/h3&gt;

&lt;p&gt;Update the Nginx config to add a second server block for port 443:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;fastchat.duckdns.org&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/live/fastchat.duckdns.org/fullchain.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/live/fastchat.duckdns.org/privkey.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://fastchat:3000&lt;/span&gt;&lt;span class="p"&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;Nginx handles the TLS handshake and decryption, then forwards plain HTTP to the FastChat container on the internal Docker network. The app never touches SSL — it just handles business logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 9 — Start Everything
&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;# Start app and databases&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/fastchat
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Run PostgreSQL migrations&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your-database-url&amp;gt; fastchat npm run migrate:up

&lt;span class="c"&gt;# Start Nginx&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/infra
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Ff6w8dfl91pk57yaitwrr.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.amazonaws.com%2Fuploads%2Farticles%2Ff6w8dfl91pk57yaitwrr.png" alt=" " width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The app is now live. You can verify with the health check endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET https://fastchat.duckdns.org/health
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"checks"&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;"mongodb"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"connected"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"postgresql"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"connected"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"redis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"connected"&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;&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.amazonaws.com%2Fuploads%2Farticles%2Fjfbi0wx2kelll8s9en5v.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.amazonaws.com%2Fuploads%2Farticles%2Fjfbi0wx2kelll8s9en5v.png" alt=" " width="800" height="229"&gt;&lt;/a&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.amazonaws.com%2Fuploads%2Farticles%2Fhtu6s4kwy9r3xhhbskue.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.amazonaws.com%2Fuploads%2Farticles%2Fhtu6s4kwy9r3xhhbskue.png" alt=" " width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges I Faced (The Real Learning)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Certbot failing because Nginx wasn't running&lt;/strong&gt;&lt;br&gt;
I kept running the Certbot command first, forgetting that Let's Encrypt needs an active HTTP server to verify the domain. Fixed by always starting Nginx before requesting a certificate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. App crashed on startup — forgot to update compose.yml after switching to S3&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I had originally built the app with local file storage. When I switched to S3, I updated the code and added the S3 variables to my &lt;code&gt;.env&lt;/code&gt; file locally — but forgot to add them to the &lt;code&gt;environment&lt;/code&gt; section in &lt;code&gt;compose.yml&lt;/code&gt;. So when the container started on EC2, the app crashed immediately because the S3 variables didn't exist inside the container.&lt;/p&gt;

&lt;p&gt;The fix was simply adding the missing variables to &lt;code&gt;compose.yml&lt;/code&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;fastchat&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;nareshlohar86/fastchat&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;AWS_REGION=your_aws_region&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS_ACCESS_KEY_ID=your_aws_access_key_id&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS_SECRET_ACCESS_KEY=your_aws_secret_access_key&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;S3_BUCKET_NAME=your_s3_bucket_name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lesson: whenever you add new environment variables to your app, update &lt;code&gt;compose.yml&lt;/code&gt; at the same time — don't leave it for later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Nginx crashed because FastChat wasn't running yet&lt;/strong&gt;&lt;br&gt;
I started the &lt;code&gt;infra&lt;/code&gt; compose before &lt;code&gt;fastchat&lt;/code&gt;. Nginx read its config, tried to resolve the &lt;code&gt;fastchat&lt;/code&gt; hostname via Docker DNS, failed because that container didn't exist yet, and crashed. Always start the app first, then the reverse proxy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. A typo that took way too long to debug&lt;/strong&gt;&lt;br&gt;
I wrote &lt;code&gt;ssl_certificate&lt;/code&gt; twice instead of &lt;code&gt;ssl_certificate&lt;/code&gt; + &lt;code&gt;ssl_certificate_key&lt;/code&gt;. Nginx threw a cryptic error and I stared at it for too long. Read your config carefully, character by character.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Forgot to run PostgreSQL migrations&lt;/strong&gt;&lt;br&gt;
Every time I redeployed with a fresh database, I forgot to run migrations. The &lt;code&gt;/auth/signup&lt;/code&gt; endpoint would fail with a table-not-found error. I've since added a reminder comment at the top of my deployment notes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Certbot failing due to HTTP → HTTPS redirect&lt;/strong&gt;&lt;br&gt;
When I first ran the Certbot command, it kept failing during domain verification. The reason: I had Nginx configured to redirect all HTTP traffic to HTTPS. But Let's Encrypt verifies your domain by sending a plain HTTP request to &lt;code&gt;/.well-known/acme-challenge/&lt;/code&gt;. That request was getting redirected to HTTPS — which didn't exist yet because I didn't have the certificate yet. Classic chicken-and-egg problem.&lt;/p&gt;

&lt;p&gt;The fix was to temporarily remove the HTTPS redirect from Nginx config, leaving only the acme-challenge block on port 80:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/.well-known/acme-challenge/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/var/www/certbot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# location / {&lt;/span&gt;
&lt;span class="c1"&gt;#     return 301 https://$host$request_uri;   ← comment this out temporarily&lt;/span&gt;
&lt;span class="c1"&gt;# }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get the certificate first, then uncomment the redirect and reload Nginx.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Accidentally starting Certbot container along with Nginx&lt;/strong&gt;&lt;br&gt;
My &lt;code&gt;infra/compose.yml&lt;/code&gt; has two services — nginx and certbot. Early on I kept running &lt;code&gt;docker compose up -d&lt;/code&gt; inside the infra folder which starts &lt;strong&gt;both&lt;/strong&gt; services together. But certbot is a one-time job — it just issues the certificate and exits. Running it repeatedly caused unnecessary errors and confusion.&lt;/p&gt;

&lt;p&gt;The fix was to start them separately:&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;# start only nginx (run this every time)&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; nginx

&lt;span class="c"&gt;# run certbot only once to issue certificate&lt;/span&gt;
docker compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; certbot certonly ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lesson: use &lt;code&gt;docker compose up -d &amp;lt;service-name&amp;gt;&lt;/code&gt; to start a specific service instead of all services at once. Certbot is not a long-running service — it does its job and exits.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Before this, I thought deploying meant just running &lt;code&gt;node index.js&lt;/code&gt; on a server somewhere. I didn't appreciate how much infrastructure sits between your code and the internet.&lt;/p&gt;

&lt;p&gt;The biggest mental shift: &lt;strong&gt;your app should not care about SSL, ports, or the outside world.&lt;/strong&gt; Nginx handles all of that. Your app just receives clean HTTP requests on an internal port. This separation makes everything simpler and more secure.&lt;/p&gt;

&lt;p&gt;The Docker networking model also clicked for me here — different networks for different trust boundaries. Databases don't need to know the internet exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Set up CI/CD with GitHub Actions (auto-deploy on push to main)&lt;/li&gt;
&lt;li&gt;Add Prometheus metrics + Grafana dashboard&lt;/li&gt;
&lt;li&gt;Refactor into microservices (Auth service, Chat service, User service)&lt;/li&gt;
&lt;li&gt;Deploy to Kubernetes (EKS)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;I'm a CS student documenting my journey from student projects to production-grade systems. If you're building something similar or have suggestions, connect with me on &lt;a href="https://www.linkedin.com/in/nareshlohar86/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or check out my code on &lt;a href="https://github.com/codephoenix86" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>webdev</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
