<?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: Adenuga Israel Abimbola</title>
    <description>The latest articles on Forem by Adenuga Israel Abimbola (@bximbo).</description>
    <link>https://forem.com/bximbo</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%2F3608093%2F9c78d61e-194d-4a22-9492-f889e9201fed.jpeg</url>
      <title>Forem: Adenuga Israel Abimbola</title>
      <link>https://forem.com/bximbo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bximbo"/>
    <language>en</language>
    <item>
      <title>HNG Portal DevOps Technical Documentation</title>
      <dc:creator>Adenuga Israel Abimbola</dc:creator>
      <pubDate>Fri, 21 Nov 2025 04:57:53 +0000</pubDate>
      <link>https://forem.com/bximbo/hng-portal-devops-technical-documentation-1li</link>
      <guid>https://forem.com/bximbo/hng-portal-devops-technical-documentation-1li</guid>
      <description>&lt;p&gt;&lt;em&gt;On behalf of my team...&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Executive Summary
&lt;/h2&gt;

&lt;p&gt;This document outlines the complete DevOps implementation for the HNG Portal project, covering CI/CD pipeline setup, server infrastructure configuration, and automated deployment workflows across staging and production environments. The project involved resolving critical infrastructure challenges, migrating services to stable AWS instances, and establishing robust automated deployment processes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Infrastructure Setup &amp;amp; Migration&lt;/li&gt;
&lt;li&gt;CI/CD Pipeline Configuration&lt;/li&gt;
&lt;li&gt;Service Persistence &amp;amp; Auto-Recovery&lt;/li&gt;
&lt;li&gt;Deployment Architecture&lt;/li&gt;
&lt;li&gt;Troubleshooting &amp;amp; Resolution Log&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Infrastructure Setup &amp;amp; Migration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 Critical Infrastructure Challenges &amp;amp; Resolution
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Initial Server Stability Issues&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We encountered significant infrastructure instability on the original server that affected both frontend and backend deployments. The issues were severe enough that we had to communicate with all stakeholders - the frontend team, backend team, and project management - about the situation. &lt;/p&gt;

&lt;p&gt;To maintain development velocity while resolving the infrastructure problems, we made the decision to provision separate AWS EC2 instances for the frontend and backend teams. This allowed both teams to continue their work uninterrupted while we systematically diagnosed and fixed the underlying server issues.&lt;/p&gt;

&lt;p&gt;Once we fully resolved all the infrastructure problems, we successfully migrated everything to the stabilized production server, consolidating our deployment architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Production Infrastructure:&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;Environment&lt;/th&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;API Domain&lt;/th&gt;
&lt;th&gt;Server IP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Staging&lt;/td&gt;
&lt;td&gt;staging.takeda.emerj.net&lt;/td&gt;
&lt;td&gt;api.staging.takeda.emerj.net&lt;/td&gt;
&lt;td&gt;54.91.35.111&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;td&gt;takeda.emerj.net&lt;/td&gt;
&lt;td&gt;api.takeda.emerj.net&lt;/td&gt;
&lt;td&gt;54.91.35.111&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  1.2 EC2 Server Configuration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Server Access &amp;amp; Authentication&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We started by setting up proper SSH access to the EC2 instances. Initially, there was confusion with the SSH command syntax - the wrong flag was being used for key authentication. We corrected this to use the proper approach:&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;# Example SSH connection approach&lt;/span&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; ~/path/to/keyfile.pem ubuntu@&amp;lt;server-ip&amp;gt;

&lt;span class="c"&gt;# Proper key permissions&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;400 ~/path/to/keyfile.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Storage Management &amp;amp; Disk Expansion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the major infrastructure issues we faced was running out of disk space, which was causing deployment failures. The root filesystem was completely full at 7.6GB out of 7.6GB, blocking critical operations like Git pulls and package installations.&lt;/p&gt;

&lt;p&gt;We resolved this by expanding the EBS volume from 8GB to 16GB and properly resizing the filesystem:&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;# Example approach to expanding partitions&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;cloud-guest-utils
&lt;span class="nb"&gt;sudo &lt;/span&gt;growpart /dev/device partition-number
&lt;span class="nb"&gt;sudo &lt;/span&gt;resize2fs /dev/partition
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the expansion, we verified the new capacity and confirmed we had sufficient space (8GB free) for normal operations. We also implemented cleanup procedures:&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;# Example cleanup approaches&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt clean
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;--vacuum-size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100M
docker system prune &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1.3 GitHub Actions Self-Hosted Runner Setup
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Runner Installation Process&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We set up self-hosted runners on our EC2 instances to enable direct deployment from GitHub Actions. The initial setup had some challenges - the runner archive wasn't properly extracted, and we encountered authentication issues during registration.&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;# Example runner setup approach&lt;/span&gt;
&lt;span class="nb"&gt;tar &lt;/span&gt;xzf actions-runner-package.tar.gz
./config.sh &lt;span class="nt"&gt;--url&lt;/span&gt; https://github.com/organization/repository &lt;span class="nt"&gt;--token&lt;/span&gt; &amp;lt;TOKEN&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Authentication Issues &amp;amp; Resolution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We hit a 404 error during runner registration initially. This happened because the runner registration token had expired - these tokens are only valid for about an hour. We had to go back to the GitHub repository settings, generate a fresh token from the Actions runner page, and immediately use it for registration. This taught us to always use tokens as soon as they're generated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runner Verification&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After successful registration, we verified the runner was showing as online in the GitHub Actions dashboard and tested that it could pick up and execute workflow jobs. We also configured deploy keys to allow secure repository access without exposing credentials.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. CI/CD Pipeline Configuration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1 Backend CI Pipeline (Laravel)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Workflow Configuration Structure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We configured the backend CI pipeline to trigger on pull requests to the staging branch, specifically watching for changes in the backend directory. The pipeline sets up a complete testing environment with MySQL service containers.&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;# Example CI workflow structure&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;Backend CI Pipeline&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;pull_request&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="s"&gt;staging&lt;/span&gt;
    &lt;span class="na"&gt;paths&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;backend/**"&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;database&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;mysql:version&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;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secure-password&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-database&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pipeline Execution Steps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The backend pipeline goes through several stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Environment Setup&lt;/strong&gt; - We configure PHP with all necessary extensions like mbstring, pdo, mysql, tokenizer, xml, ctype, and fileinfo that Laravel requires.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dependency Management&lt;/strong&gt; - Composer installs all backend dependencies using production optimization flags.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Application Configuration&lt;/strong&gt; - The pipeline copies environment configuration templates and generates application keys.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Database Preparation&lt;/strong&gt; - Migrations run to set up the test database schema.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testing&lt;/strong&gt; - The test suite executes to validate code quality before deployment.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2.2 Frontend CI Pipeline (Next.js)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Trigger Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The frontend pipeline is set up to trigger on both pull requests and direct pushes to the staging branch. We configured path-specific triggers so the pipeline only runs when there are actual frontend code changes, saving runner resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build Process&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The frontend build process involves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Installing dependencies using pnpm&lt;/li&gt;
&lt;li&gt;Building the Next.js application&lt;/li&gt;
&lt;li&gt;Verifying build artifacts are created successfully&lt;/li&gt;
&lt;li&gt;Preparing for deployment to the self-hosted runner&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2.3 Production CI Configuration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Branch Protection &amp;amp; Quality Gates&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For the production branch, we implemented stricter controls. All CI checks must pass before any code can be merged. This ensures that only tested, verified code makes it to production. The deployment happens automatically when changes are successfully merged to the prod branch.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Service Persistence &amp;amp; Auto-Recovery
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 Backend Service Configuration (Systemd)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Production Backend Service&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The backend API runs as a systemd service, which gives us powerful process management capabilities including automatic restarts on failure and startup on server reboot.&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;# Example systemd service structure&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;Unit]
&lt;span class="nv"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Production Backend API Service
&lt;span class="nv"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;network.target

&lt;span class="o"&gt;[&lt;/span&gt;Service]
&lt;span class="nv"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;simple
&lt;span class="nv"&gt;User&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;www-data
&lt;span class="nv"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/www/production/backend
&lt;span class="nv"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/node server.js
&lt;span class="nv"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on-failure
&lt;span class="nv"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;span class="nv"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6001

&lt;span class="o"&gt;[&lt;/span&gt;Install]
&lt;span class="nv"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;multi-user.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;WantedBy=multi-user.target&lt;/code&gt; directive ensures the service starts automatically when the server boots. The &lt;code&gt;Restart=on-failure&lt;/code&gt; policy means if the backend crashes for any reason, systemd will automatically restart it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Service Management&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We use standard systemd commands to manage the backend services:&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;# Example service management&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;backend-service
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start backend-service
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status backend-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Path Configuration Challenge&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One significant issue we encountered was that systemd doesn't inherit the user's shell PATH. This caused failures when systemd tried to execute Node or pnpm commands that were installed via NVM. We had to explicitly specify full paths or configure the PATH environment variable in the service file.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 Frontend Service Configuration (PM2)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why PM2 for Frontend&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The frontend applications run under PM2 rather than systemd. PM2 provides excellent process management for Node.js applications with features like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatic restart on crashes&lt;/li&gt;
&lt;li&gt;Built-in log management&lt;/li&gt;
&lt;li&gt;Zero-downtime reloads&lt;/li&gt;
&lt;li&gt;Easy clustering capabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;PM2 Startup Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We configured PM2 to start automatically on system boot:&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;# Example PM2 startup configuration&lt;/span&gt;
pm2 startup systemd
pm2 save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates a systemd service that launches PM2 on boot, which in turn starts all saved PM2 processes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PM2 Troubleshooting&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We encountered an interesting issue where PM2 was trying to execute the pnpm binary as if it were a Node script, causing syntax errors. The problem was that pnpm is actually a shell script, not JavaScript. We resolved this by ensuring PM2 was configured to execute the correct commands with proper interpreters.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 PHP Composer Installation for Backend
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Composer Setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Laravel backend required Composer for dependency management. We installed it globally so it's accessible system-wide:&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;# Example Composer installation approach&lt;/span&gt;
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; https://getcomposer.org/installer &lt;span class="nt"&gt;-o&lt;/span&gt; composer-setup.php
&lt;span class="nb"&gt;sudo &lt;/span&gt;php composer-setup.php &lt;span class="nt"&gt;--install-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/bin &lt;span class="nt"&gt;--filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;composer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our case, we were also using Herd Lite, which provided its own Composer binary at a specific location. We ensured the correct binary path was being used in our deployment scripts.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Deployment Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 Port Allocation Strategy
&lt;/h3&gt;

&lt;p&gt;We established a clear port allocation scheme to avoid conflicts and make service management straightforward:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment&lt;/th&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;4001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;6001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Staging&lt;/td&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;4000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Staging&lt;/td&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;6000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This layout makes it easy to remember: staging uses the x000 ports, production uses x001 ports. Backend services are on 6xxx, frontend on 4xxx.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Nginx Reverse Proxy Configuration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure Recovery&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We faced a critical issue where the entire Nginx installation was corrupted - the main configuration file and directory structure were missing. This was blocking all deployments since Nginx couldn't even start.&lt;/p&gt;

&lt;p&gt;We had to perform a complete Nginx reinstallation:&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;# Example complete Nginx reinstallation approach&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt purge nginx nginx-common nginx-full &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /etc/nginx
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;nginx &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This recreated the proper directory structure with all default configuration files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Production Server Blocks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We configured Nginx to reverse proxy to our frontend and backend services. Here's the conceptual approach for the production configuration:&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="c1"&gt;# Example production frontend proxy&lt;/span&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;takeda.emerj.net&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://localhost:4001&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;'upgrade'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_cache_bypass&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;# Additional proxy headers for client info&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Example production backend API proxy&lt;/span&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;api.takeda.emerj.net&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://localhost:6001&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;# Similar proxy configuration&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;&lt;strong&gt;Staging Server Blocks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The staging configuration follows the same pattern but uses different domains and ports:&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="c1"&gt;# Example staging frontend proxy&lt;/span&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;staging.takeda.emerj.net&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://localhost:4000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;# Proxy configuration&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Example staging backend API proxy&lt;/span&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;api.staging.takeda.emerj.net&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://localhost:6000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;# Proxy configuration&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;&lt;strong&gt;Site Activation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After creating the configuration files, we enabled them and verified everything was correct:&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;# Example site enablement approach&lt;/span&gt;
&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/nginx/sites-available/production /etc/nginx/sites-enabled/
&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/nginx/sites-available/staging /etc/nginx/sites-enabled/
&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.3 Logging Configuration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Nginx Log Setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We needed to ensure proper log directories existed with correct permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example log directory setup&lt;/span&gt;
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /var/log/nginx
&lt;span class="nb"&gt;sudo touch&lt;/span&gt; /var/log/nginx/error.log
&lt;span class="nb"&gt;sudo touch&lt;/span&gt; /var/log/nginx/access.log
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; www-data:www-data /var/log/nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows us to monitor access patterns and debug any proxy issues.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Troubleshooting &amp;amp; Resolution Log
&lt;/h2&gt;

&lt;h3&gt;
  
  
  5.1 Critical Issues Encountered &amp;amp; Resolved
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Issue 1: Complete Disk Full - Git Operations Failing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Git pull commands were failing with "No space left on device" errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Investigation:&lt;/strong&gt; We ran disk usage commands and found the root filesystem was at 100% capacity - completely full at 7.6GB with 0 bytes available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resolution:&lt;/strong&gt; We implemented a multi-step cleanup process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cleared apt package cache to free immediate space&lt;/li&gt;
&lt;li&gt;Vacuumed systemd journal logs that were consuming significant space&lt;/li&gt;
&lt;li&gt;Removed unused Docker containers and images&lt;/li&gt;
&lt;li&gt;Finally expanded the EBS volume and resized the filesystem as detailed earlier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Issue 2: pnpm Not Found in Systemd Context&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Backend service was failing with "Failed to locate executable /usr/bin/pnpm"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Investigation:&lt;/strong&gt; We discovered that pnpm was installed via NVM in the user's home directory, not in a system-wide location. Systemd was looking for it at /usr/bin/pnpm which didn't exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resolution:&lt;/strong&gt; We updated the systemd service configuration to either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the full path to the pnpm binary in the NVM directory&lt;/li&gt;
&lt;li&gt;Set the PATH environment variable in the service file to include the NVM binary directory&lt;/li&gt;
&lt;li&gt;Or use a shell wrapper that loads the full environment before executing commands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Issue 3: PM2 Crash Loop with Syntax Errors&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; PM2 logs showed repeated "SyntaxError: missing ) after argument list" errors, with the process constantly restarting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Investigation:&lt;/strong&gt; We analyzed the logs and discovered that Node.js was trying to execute the pnpm shell script directly as JavaScript code, which obviously failed since pnpm is a bash script, not a Node module.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resolution:&lt;/strong&gt; We corrected the PM2 configuration to properly invoke pnpm through the shell rather than trying to run it as a Node script. This involved adjusting how the command was being called.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issue 4: Nginx Configuration Completely Missing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Running &lt;code&gt;nginx -t&lt;/code&gt; produced "No such file or directory" errors for nginx.conf.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Investigation:&lt;/strong&gt; We checked the /etc/nginx directory and found it was either missing entirely or had been corrupted - the main configuration file and critical subdirectories like sites-available and sites-enabled didn't exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resolution:&lt;/strong&gt; We performed a complete purge and reinstall of Nginx. This was the only way to restore the proper directory structure and default configuration files. After reinstallation, we rebuilt our custom configurations from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issue 5: 502 Bad Gateway from Nginx&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Visiting the application URL resulted in a 502 Bad Gateway error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Investigation:&lt;/strong&gt; We verified that Nginx was running properly but discovered the backend application wasn't actually listening on the expected port. The service was failing to start but failing silently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resolution:&lt;/strong&gt; We used systemctl status and journalctl to examine the service logs, identified why it wasn't starting (usually environment or path issues), fixed the configuration, and restarted the service. Once the application was properly listening on its port, Nginx could successfully proxy to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issue 6: GitHub Runner 404 During Registration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Attempting to register the self-hosted runner returned a 404 Not Found error from GitHub's API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Investigation:&lt;/strong&gt; This can happen for several reasons - wrong repository URL, wrong token type (repo vs organization), or most commonly, an expired token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resolution:&lt;/strong&gt; We went back to the repository's Settings &amp;gt; Actions &amp;gt; Runners page, generated a fresh registration token, and immediately used it to register the runner. Registration tokens expire after about an hour, so timing is important.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issue 7: Hostname Resolution Warnings in Sudo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Every sudo command showed "unable to resolve host" warnings, though commands still executed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Investigation:&lt;/strong&gt; We found that the server's hostname wasn't properly listed in the /etc/hosts file, so the system couldn't resolve its own hostname.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resolution:&lt;/strong&gt; We edited /etc/hosts to include an entry mapping the hostname to 127.0.1.1, which resolved the warning messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 Deployment Verification Process
&lt;/h3&gt;

&lt;p&gt;After making changes, we established a verification checklist to ensure everything was working:&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;# Example verification commands&lt;/span&gt;

&lt;span class="c"&gt;# Check frontend services&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status staging-frontend
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status prod-frontend
pm2 status

&lt;span class="c"&gt;# Check backend services  &lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status staging-backend
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status prod-backend

&lt;span class="c"&gt;# Verify Nginx configuration and status&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status nginx

&lt;span class="c"&gt;# Confirm services are listening on correct ports&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;netstat &lt;span class="nt"&gt;-tlnp&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;':(4000|4001|6000|6001)'&lt;/span&gt;

&lt;span class="c"&gt;# Monitor live logs for issues&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; prod-backend &lt;span class="nt"&gt;-f&lt;/span&gt;
pm2 logs
&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/nginx/error.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. Deployment Workflow Summary
&lt;/h2&gt;

&lt;h3&gt;
  
  
  6.1 Automated Deployment Process
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Staging Deployment Flow&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a developer pushes code to the staging branch, here's what happens automatically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GitHub detects the push and triggers the appropriate CI workflow&lt;/li&gt;
&lt;li&gt;Our self-hosted runner picks up the job&lt;/li&gt;
&lt;li&gt;The runner checks out the code&lt;/li&gt;
&lt;li&gt;Dependencies are installed (pnpm for frontend, composer for backend)&lt;/li&gt;
&lt;li&gt;The application is built&lt;/li&gt;
&lt;li&gt;For frontend: PM2 reloads the application with zero downtime&lt;/li&gt;
&lt;li&gt;For backend: The systemd service is restarted&lt;/li&gt;
&lt;li&gt;Nginx continues serving traffic throughout the process&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Production Deployment Flow&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Production deployments follow a similar but more controlled process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Changes are merged to the prod branch after passing all staging tests&lt;/li&gt;
&lt;li&gt;The production CI workflow executes&lt;/li&gt;
&lt;li&gt;Build artifacts are generated with production optimizations&lt;/li&gt;
&lt;li&gt;Services are updated using the same reload/restart mechanisms&lt;/li&gt;
&lt;li&gt;We monitor logs immediately after deployment to catch any issues&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  6.2 Server Reboot Survival
&lt;/h3&gt;

&lt;p&gt;All our services are configured to automatically start after a server reboot. This is critical for maintaining uptime during planned maintenance or unexpected restarts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend (PM2):&lt;/strong&gt; The PM2 startup script runs at boot and launches all saved PM2 processes, including both staging and production frontends.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend (Systemd):&lt;/strong&gt; The systemd services have &lt;code&gt;WantedBy=multi-user.target&lt;/code&gt;, which means they start automatically during the normal multi-user boot sequence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nginx:&lt;/strong&gt; The Nginx service is enabled and starts automatically, ready to proxy requests as soon as the backend and frontend services are available.&lt;/p&gt;

&lt;p&gt;We verified this works by actually performing test reboots of the server and confirming all services came back online without manual intervention.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Security Considerations
&lt;/h2&gt;

&lt;p&gt;Throughout the deployment setup, we implemented several security best practices:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSH Key Security:&lt;/strong&gt; All SSH keys are kept with restrictive permissions (chmod 400) so only the owner can read them. This prevents unauthorized access even if someone gains access to the filesystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Service User Isolation:&lt;/strong&gt; Backend services run under the www-data user rather than root, following the principle of least privilege. This limits the damage if a service is compromised.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Environment Variables:&lt;/strong&gt; Sensitive configuration like database passwords and API keys are stored in systemd service files or PM2 ecosystem files, not in version control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nginx as Security Layer:&lt;/strong&gt; The Nginx reverse proxy provides an additional security boundary between the internet and our application services. It also allows us to easily add features like rate limiting or SSL termination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database Isolation:&lt;/strong&gt; The MySQL service is configured to only accept connections from localhost, preventing external database access.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Maintenance &amp;amp; Monitoring
&lt;/h2&gt;

&lt;h3&gt;
  
  
  8.1 Health Check Procedures
&lt;/h3&gt;

&lt;p&gt;We established regular health check procedures to catch issues before they become critical:&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;# Example system health check approach&lt;/span&gt;
systemctl list-units &lt;span class="nt"&gt;--state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;failed
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
free &lt;span class="nt"&gt;-h&lt;/span&gt;

&lt;span class="c"&gt;# Application-specific checks&lt;/span&gt;
pm2 status
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; prod-backend &lt;span class="nt"&gt;--since&lt;/span&gt; &lt;span class="s2"&gt;"1 hour ago"&lt;/span&gt;
&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/nginx/error.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  8.2 Log Management
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Nginx Logs:&lt;/strong&gt; Access logs help us track traffic patterns, while error logs are crucial for debugging proxy issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Systemd Journal:&lt;/strong&gt; Backend services log to the systemd journal, which we can query with journalctl. We keep these rotated to prevent disk space issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PM2 Logs:&lt;/strong&gt; Frontend logs are managed by PM2, which provides easy access through the &lt;code&gt;pm2 logs&lt;/code&gt; command and automatically rotates log files.&lt;/p&gt;

&lt;h3&gt;
  
  
  8.3 Backup &amp;amp; Recovery
&lt;/h3&gt;

&lt;p&gt;While not explicitly detailed in the troubleshooting above, proper backup procedures are essential. We ensure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Regular database backups&lt;/li&gt;
&lt;li&gt;Code is in version control (not relying on server copies)&lt;/li&gt;
&lt;li&gt;Configuration files are documented (like in this document)&lt;/li&gt;
&lt;li&gt;AMI snapshots of the EC2 instance for disaster recovery&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  9. Repository &amp;amp; Live Application
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Repository Structure:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Staging Branch CI: &lt;code&gt;.github/workflows/backend-ci-staging.yml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Production Branch CI: Configured with appropriate branch protection rules&lt;/li&gt;
&lt;li&gt;Deploy keys configured for secure repository access&lt;/li&gt;
&lt;li&gt;Self-hosted runner active and registered&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Live Application URLs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Staging Frontend:&lt;/strong&gt; staging.takeda.emerj.net&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Staging Backend API:&lt;/strong&gt; api.staging.takeda.emerj.net&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production Frontend:&lt;/strong&gt; takeda.emerj.net&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production Backend API:&lt;/strong&gt; api.takeda.emerj.net&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Server Infrastructure:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All services hosted on: 54.91.35.111&lt;/li&gt;
&lt;li&gt;Multiple environment isolation via port allocation&lt;/li&gt;
&lt;li&gt;Services configured for automatic startup and failure recovery&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  10. Lessons Learned &amp;amp; Best Practices
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Always Use Fresh Tokens:&lt;/strong&gt; We learned that GitHub runner registration tokens expire quickly. Generate and use them immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test Reboot Survival:&lt;/strong&gt; Don't assume services will start on reboot - actually test it by restarting the server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitor Disk Space:&lt;/strong&gt; Many mysterious issues trace back to full disks. Regular monitoring and cleanup procedures are essential.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Document Port Allocations:&lt;/strong&gt; Clear port allocation schemes prevent conflicts and make troubleshooting much easier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Systemd PATH Matters:&lt;/strong&gt; Never assume systemd services inherit your shell environment. Always explicitly configure PATH and environment variables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep Nginx Configs Simple:&lt;/strong&gt; Start with basic working configurations and add complexity gradually. This makes debugging much easier.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This DevOps implementation successfully delivers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fully automated CI/CD pipelines&lt;/strong&gt; for both staging and production environments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service persistence across reboots&lt;/strong&gt; using systemd and PM2 with proper startup configurations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable architecture&lt;/strong&gt; with clear environment separation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comprehensive logging&lt;/strong&gt; for troubleshooting and  monitoring&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Robust error recovery&lt;/strong&gt; with automatic restart mechanisms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production-ready infrastructure&lt;/strong&gt; that survived rigorous testing including server reboots&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The infrastructure migration from unstable servers to our current provided consolidated instance was complex but ultimately successful. We maintained development velocity by providing temporary separate environments while systematically resolving all infrastructure issues. The final architecture is production-ready, well-documented, and built to survive the real-world challenges of maintaining uptime and deployability.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Ps: all code snippets only show the thought process towards the approach and not what was used :)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>stage5</category>
      <category>githubactions</category>
      <category>cicd</category>
      <category>mysql</category>
    </item>
    <item>
      <title>Building Your Own Virtual Private Cloud (VPC) on Linux</title>
      <dc:creator>Adenuga Israel Abimbola</dc:creator>
      <pubDate>Wed, 12 Nov 2025 15:36:11 +0000</pubDate>
      <link>https://forem.com/bximbo/building-your-own-virtual-private-cloud-vpc-on-linux-4hje</link>
      <guid>https://forem.com/bximbo/building-your-own-virtual-private-cloud-vpc-on-linux-4hje</guid>
      <description>&lt;p&gt;Have you ever wondered what really happens when you create a Virtual Private Cloud (VPC) on AWS or GCP?&lt;br&gt;
Behind the glossy dashboards and checkboxes, it’s all Linux,  namespaces, bridges, and routing tables quietly working together.&lt;/p&gt;

&lt;p&gt;In this guide, you’ll build your own local version of a cloud VPC.&lt;br&gt;
You’ll use &lt;strong&gt;Linux network namespaces&lt;/strong&gt; to create isolated environments, &lt;strong&gt;bridges&lt;/strong&gt; to connect them, and &lt;strong&gt;iptables&lt;/strong&gt; to manage NAT and firewall behavior.&lt;br&gt;
And to tie it all together, you’ll run a small Python controller that automates the whole thing for you.&lt;/p&gt;

&lt;p&gt;By the time you’re done, you’ll understand how cloud networking actually works, not just what buttons to click.&lt;/p&gt;


&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;We’ll be using a simple setup called &lt;strong&gt;Bimbo VPC Controller&lt;/strong&gt;.&lt;br&gt;
It’s a command-line utility that lets you create virtual networks, connect them, enforce rules, and then clean them up again — all on your local machine.&lt;/p&gt;

&lt;p&gt;Here’s the structure of the project folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/vpcctl-stage-4
│
├── demo.sh          # Runs the full demo automatically
├── policy.json      # Contains firewall and access policies
├── vpcctl.py        # Main Python controller script
└── README.md        # Documentation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;Let’s break down how the system fits together.&lt;/p&gt;

&lt;p&gt;Each VPC is represented as a &lt;strong&gt;network namespace&lt;/strong&gt;.&lt;br&gt;
Within each VPC, there are two subnets — one public and one private — each connected to its own &lt;strong&gt;Linux bridge&lt;/strong&gt;.&lt;br&gt;
The bridges are connected to your host interface (like &lt;code&gt;ens5&lt;/code&gt;) through &lt;strong&gt;veth pairs&lt;/strong&gt;.&lt;br&gt;
NAT and firewall rules are applied using &lt;strong&gt;iptables&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here’s a simple sketch of what that looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                +-------------------+
                |   VPC Namespace   |
                |  (vpc1 / vpc2)    |
                +---------+---------+
                          |
                     veth pair
                          |
                +---------+---------+
                |   Linux Bridge    |
                |  (br-vpc1-pub)    |
                +---------+---------+
                          |
                 Public / Private Subnets
                          |
                +---------+---------+
                |  NAT / Firewall   |
                | (iptables rules)  |
                +---------+---------+
                          |
                        Internet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So when you create VPC1, you’re really creating a namespace with its own virtual network and routing.&lt;br&gt;
It behaves just like an AWS VPC, only it’s running on your laptop.&lt;/p&gt;


&lt;h2&gt;
  
  
  Setting Up
&lt;/h2&gt;

&lt;p&gt;First, clone the project from GitHub and move into the directory:&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 https://github.com/&amp;lt;your-username&amp;gt;/vpcctl-stage-4.git
&lt;span class="nb"&gt;cd &lt;/span&gt;vpcctl-stage-4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make the scripts executable so they can run directly:&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;chmod&lt;/span&gt; +x demo.sh vpcctl.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you’d like to make the Python script available globally, create a symbolic link:&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;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/vpcctl.py"&lt;/span&gt; /usr/local/bin/vpcctl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, install the required dependencies:&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;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; iproute2 iptables curl netcat python3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’re ready to go.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running the Demo
&lt;/h2&gt;

&lt;p&gt;The easiest way to see everything in action is by running the demo script.&lt;br&gt;
It handles creation, configuration, and cleanup automatically.&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;sudo&lt;/span&gt; ./demo.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create two VPCs (vpc1 and vpc2),&lt;/li&gt;
&lt;li&gt;Add public and private subnets to each,&lt;/li&gt;
&lt;li&gt;Launch small web servers inside them,&lt;/li&gt;
&lt;li&gt;Enable NAT on the public subnets,&lt;/li&gt;
&lt;li&gt;Apply firewall policies,&lt;/li&gt;
&lt;li&gt;Test connectivity and isolation,&lt;/li&gt;
&lt;li&gt;Then tear everything down.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can watch every step unfold in your terminal as it happens.&lt;/p&gt;




&lt;h2&gt;
  
  
  Command Line Usage
&lt;/h2&gt;

&lt;p&gt;If you’d rather control things manually, you can use the &lt;code&gt;vpcctl&lt;/code&gt; commands yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a VPC
&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;sudo &lt;/span&gt;vpcctl create vpc1 &lt;span class="nt"&gt;--cidr&lt;/span&gt; 10.0.0.0/16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a Linux namespace named &lt;code&gt;vpc1&lt;/code&gt; and assigns it a network range.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Subnets
&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;sudo &lt;/span&gt;vpcctl add-subnet vpc1 public &lt;span class="nt"&gt;--cidr&lt;/span&gt; 10.0.1.0/24
&lt;span class="nb"&gt;sudo &lt;/span&gt;vpcctl add-subnet vpc1 private &lt;span class="nt"&gt;--cidr&lt;/span&gt; 10.0.2.0/24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each subnet gets its own bridge, connected through a veth pair.&lt;br&gt;
This is how communication inside the VPC happens.&lt;/p&gt;
&lt;h3&gt;
  
  
  Launch a Web Server
&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;sudo &lt;/span&gt;vpcctl launch-http vpc1 public &lt;span class="nt"&gt;--port&lt;/span&gt; 80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Starts a lightweight Python HTTP server inside the namespace.&lt;br&gt;
You can test it with &lt;code&gt;curl&lt;/code&gt; from another namespace later.&lt;/p&gt;
&lt;h3&gt;
  
  
  Apply a Firewall Policy
&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;sudo &lt;/span&gt;vpcctl apply-policy policy.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The controller reads your policy file and uses iptables to allow or block specific traffic.&lt;/p&gt;
&lt;h3&gt;
  
  
  Peer Two VPCs
&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;sudo &lt;/span&gt;vpcctl peer vpc1 vpc2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This connects vpc1 and vpc2 through a virtual link so they can route traffic to each other.&lt;/p&gt;


&lt;h2&gt;
  
  
  Testing and Validation
&lt;/h2&gt;

&lt;p&gt;After you’ve run the demo or created things manually, it’s time to verify everything is working as expected.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Connectivity Test Within a VPC
&lt;/h3&gt;

&lt;p&gt;From the public subnet:&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;sudo &lt;/span&gt;ip netns &lt;span class="nb"&gt;exec &lt;/span&gt;vpc1-pub ping &lt;span class="nt"&gt;-c&lt;/span&gt; 2 10.0.2.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should succeed, confirming that the subnets in the same VPC can communicate.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. NAT Test
&lt;/h3&gt;

&lt;p&gt;From the public subnet:&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;sudo &lt;/span&gt;ip netns &lt;span class="nb"&gt;exec &lt;/span&gt;vpc1-pub curl http://example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should work because NAT is enabled for the public subnet.&lt;/p&gt;

&lt;p&gt;From the private subnet:&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;sudo &lt;/span&gt;ip netns &lt;span class="nb"&gt;exec &lt;/span&gt;vpc1-priv curl http://example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should fail — private subnets are isolated from the internet by design.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Isolation Test Between VPCs
&lt;/h3&gt;

&lt;p&gt;Before peering:&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;sudo &lt;/span&gt;ip netns &lt;span class="nb"&gt;exec &lt;/span&gt;vpc1-pub ping &lt;span class="nt"&gt;-c&lt;/span&gt; 2 10.1.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should fail, since vpc1 and vpc2 have no route between them.&lt;/p&gt;

&lt;p&gt;After peering:&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;sudo &lt;/span&gt;ip netns &lt;span class="nb"&gt;exec &lt;/span&gt;vpc1-pub ping &lt;span class="nt"&gt;-c&lt;/span&gt; 2 10.1.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should now succeed.&lt;br&gt;
You’ve just simulated cross-VPC communication.&lt;/p&gt;


&lt;h2&gt;
  
  
  Firewall and Policy Test
&lt;/h2&gt;

&lt;p&gt;Let’s edit the policy file:&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;"vpc1"&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;"allow"&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="s2"&gt;"80/tcp"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deny"&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="s2"&gt;"22/tcp"&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="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;Apply it using:&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;sudo &lt;/span&gt;vpcctl apply-policy policy.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now test:&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;sudo &lt;/span&gt;ip netns &lt;span class="nb"&gt;exec &lt;/span&gt;vpc2-pub nc &lt;span class="nt"&gt;-vz&lt;/span&gt; 10.0.1.1 80
&lt;span class="nb"&gt;sudo &lt;/span&gt;ip netns &lt;span class="nb"&gt;exec &lt;/span&gt;vpc2-pub nc &lt;span class="nt"&gt;-vz&lt;/span&gt; 10.0.1.1 22
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first should connect, the second should fail.&lt;br&gt;
That’s your firewall doing its job.&lt;/p&gt;


&lt;h2&gt;
  
  
  Cleaning Up
&lt;/h2&gt;

&lt;p&gt;When you’re done experimenting, you can safely delete everything and return your system to normal.&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;sudo &lt;/span&gt;vpcctl delete vpc1
&lt;span class="nb"&gt;sudo &lt;/span&gt;vpcctl delete vpc2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or simply use the demo’s built-in cleanup command:&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;sudo&lt;/span&gt; ./demo.sh clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This removes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All network namespaces&lt;/li&gt;
&lt;li&gt;All bridges and veth pairs&lt;/li&gt;
&lt;li&gt;All iptables rules&lt;/li&gt;
&lt;li&gt;All logs and state files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clean, complete, and ready for another experiment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;You’ve just built a fully functional cloud-style network stack — using nothing but Linux.&lt;br&gt;
You saw how namespaces create isolation, how bridges connect subnets, and how iptables acts as your NAT gateway and firewall.&lt;/p&gt;

&lt;p&gt;This project gives you more than a working script — it gives you intuition.&lt;br&gt;
You now understand what happens when AWS says “launch VPC,” “add subnet,” or “enable NAT.”&lt;/p&gt;

&lt;p&gt;And the best part?&lt;br&gt;
You built it all locally, from the ground up.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;:)&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>vpc</category>
    </item>
  </channel>
</rss>
