<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ifeanyi Nworji</title>
    <description>The latest articles on DEV Community by Ifeanyi Nworji (@ifeanyi_nworji).</description>
    <link>https://dev.to/ifeanyi_nworji</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%2F1832774%2Fc7139dd0-ceb9-4b43-be96-77cc04b39b5d.jpg</url>
      <title>DEV Community: Ifeanyi Nworji</title>
      <link>https://dev.to/ifeanyi_nworji</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ifeanyi_nworji"/>
    <language>en</language>
    <item>
      <title>Blue/Green Deployment with Nginx Upstreams Using Docker Compose</title>
      <dc:creator>Ifeanyi Nworji</dc:creator>
      <pubDate>Sat, 13 Dec 2025 07:37:02 +0000</pubDate>
      <link>https://dev.to/ifeanyi_nworji/bluegreen-deployment-with-nginx-upstreams-using-docker-compose-5cn1</link>
      <guid>https://dev.to/ifeanyi_nworji/bluegreen-deployment-with-nginx-upstreams-using-docker-compose-5cn1</guid>
      <description>&lt;p&gt;&lt;strong&gt;Auto-Failover, Zero Downtime, and Manual Traffic Switching&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the core responsibilities of a DevOps engineer is ensuring application availability in the presence of failure. Downtime is rarely caused by deployments themselves, but by how traffic is handled when something goes wrong.&lt;/p&gt;

&lt;p&gt;In Stage 2 of my DevOps internship, I implemented a Blue/Green deployment architecture using Nginx upstreams and Docker Compose, focusing on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero failed client requests during outages&lt;/li&gt;
&lt;li&gt;Automatic failover within a single request&lt;/li&gt;
&lt;li&gt;Manual traffic switching without restarting containers&lt;/li&gt;
&lt;li&gt;No application code changes&lt;/li&gt;
&lt;li&gt;No image rebuilds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article is a beginner-friendly but production-accurate walkthrough of the solution, explaining both the configuration and the runtime behavior in detail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem Overview&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We are provided with two identical Node.js services packaged as pre-built Docker images:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blue — primary (active)&lt;/li&gt;
&lt;li&gt;Green — backup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each service exposes the following endpoints:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns JSON + headers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /healthz&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Liveness check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST /chaos/start&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Simulates failure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST /chaos/stop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Restores service&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The task is to place Nginx in front of both services and guarantee:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All traffic goes to Blue by default&lt;/li&gt;
&lt;li&gt;On Blue failure, Nginx automatically switches to Green&lt;/li&gt;
&lt;li&gt;No client request returns non-200 during failover&lt;/li&gt;
&lt;li&gt;Application response headers are forwarded unchanged&lt;/li&gt;
&lt;li&gt;Traffic can be manually toggled between Blue and Green&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Architecture Overview&lt;/strong&gt;&lt;br&gt;
The final architecture is intentionally simple and production-aligned:&lt;/p&gt;

&lt;p&gt;Client --&amp;gt; Nginx (8080) --&amp;gt; Blue App (8081) OR failover to Green App (8082)&lt;/p&gt;

&lt;p&gt;Key characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nginx is the single public entrypoint&lt;/li&gt;
&lt;li&gt;Blue/Green run simultaneously&lt;/li&gt;
&lt;li&gt;Docker Compose orchestrates everything&lt;/li&gt;
&lt;li&gt;No Kubernetes, no service mesh, no rebuilds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Environment-Driven Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All behavior is controlled via environment variables, making the setup CI-friendly and reproducible.&lt;/p&gt;

&lt;p&gt;Key variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BLUE_IMAGE, GREEN_IMAGE&lt;/li&gt;
&lt;li&gt;ACTIVE_POOL (blue or green)&lt;/li&gt;
&lt;li&gt;RELEASE_ID_BLUE, RELEASE_ID_GREEN&lt;/li&gt;
&lt;li&gt;PORT, BLUE_PORT, GREEN_PORT&lt;/li&gt;
&lt;li&gt;NGINX_PORT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This design ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No hardcoded values&lt;/li&gt;
&lt;li&gt;Safe traffic switching&lt;/li&gt;
&lt;li&gt;Easy automated verification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Docker Compose: Service Breakdown&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Blue Application Service&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;app_blue&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;${BLUE_IMAGE}&lt;/span&gt;
  &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app_blue&lt;/span&gt;
  &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&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;PORT=${PORT}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RELEASE_ID=${RELEASE_ID_BLUE}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;APP_POOL=blue&lt;/span&gt;
  &lt;span class="na"&gt;expose&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;${PORT}"&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${BLUE_PORT}:${PORT}"&lt;/span&gt;
  &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-e&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;process.exit(0)&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2s&lt;/span&gt;
    &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Runs the provided Blue image without modification&lt;/li&gt;
&lt;li&gt;Injects runtime metadata used in response headers&lt;/li&gt;
&lt;li&gt;Exposes the service internally to Nginx&lt;/li&gt;
&lt;li&gt;Maps a direct port (8081) for chaos testing&lt;/li&gt;
&lt;li&gt;Keeps the container healthy and restartable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Green service is identical, differing only in image, release ID, and port.&lt;br&gt;
This symmetry is critical for Blue/Green deployments.&lt;/p&gt;

&lt;p&gt;Nginx Reverse Proxy&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;nginx&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;nginx:latest&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${NGINX_PORT}:80"&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;./nginx/nginx.tmpl:/etc/nginx/templates/default.conf.template:ro&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx/entrypoint.sh:/docker-entrypoint.d/10-envsubst.sh:ro&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx-logs:/var/log/nginx&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;ACTIVE_POOL=${ACTIVE_POOL}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PORT=${PORT}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nginx is the only public interface&lt;/li&gt;
&lt;li&gt;Configuration is templated, not static&lt;/li&gt;
&lt;li&gt;Logs are persisted for inspection and alerting&lt;/li&gt;
&lt;li&gt;No container restarts needed for traffic switching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Nginx Upstreams: Blue/Green Routing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The heart of the solution lies in the Nginx upstream configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timeout and Retry Configuration&lt;/strong&gt;&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;proxy_connect_timeout&lt;/span&gt; &lt;span class="s"&gt;1s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;proxy_read_timeout&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;proxy_send_timeout&lt;/span&gt; &lt;span class="s"&gt;3s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;proxy_next_upstream&lt;/span&gt; &lt;span class="s"&gt;error&lt;/span&gt; &lt;span class="s"&gt;timeout&lt;/span&gt; &lt;span class="s"&gt;http_500&lt;/span&gt; &lt;span class="s"&gt;http_502&lt;/span&gt; &lt;span class="s"&gt;http_503&lt;/span&gt; &lt;span class="s"&gt;http_504&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;proxy_next_upstream_tries&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;proxy_next_upstream_timeout&lt;/span&gt; &lt;span class="s"&gt;8s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These values ensure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Failures are detected quickly&lt;/li&gt;
&lt;li&gt;Retries happen automatically&lt;/li&gt;
&lt;li&gt;Total request time remains under 10 seconds&lt;/li&gt;
&lt;li&gt;Clients never see partial or failed responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Primary / Backup Upstreams&lt;/strong&gt;&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;upstream&lt;/span&gt; &lt;span class="s"&gt;blue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;app_blue:&lt;/span&gt;$&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;PORT&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="s"&gt;max_fails=1&lt;/span&gt; &lt;span class="s"&gt;fail_timeout=5s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;app_green:&lt;/span&gt;$&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;PORT&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="s"&gt;backup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kn"&gt;upstream&lt;/span&gt; &lt;span class="s"&gt;green&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;app_green:&lt;/span&gt;$&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;PORT&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="s"&gt;max_fails=1&lt;/span&gt; &lt;span class="s"&gt;fail_timeout=5s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;app_blue:&lt;/span&gt;$&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;PORT&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="s"&gt;backup&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;Why this works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;max_fails=1 marks the primary unhealthy after a single failure&lt;/li&gt;
&lt;li&gt;fail_timeout=5s enables fast recovery&lt;/li&gt;
&lt;li&gt;backup ensures Green is only used when Blue fails&lt;/li&gt;
&lt;li&gt;The same config supports both active pools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Deep Dive: Request Flow During Failure&lt;/strong&gt;&lt;br&gt;
This is the most important part of the system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Normal Operation&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client sends:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET http://localhost:8080/version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Nginx forwards to Blue&lt;/li&gt;
&lt;li&gt;Blue responds with 200&lt;/li&gt;
&lt;li&gt;Headers returned:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X-App-Pool: blue
X-Release-Id: &amp;lt;RELEASE_ID_BLUE&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Failure Scenario (Blue Down)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Chaos is induced directly on Blue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;POST http://localhost:8081/chaos/start?mode&lt;span class="o"&gt;=&lt;/span&gt;error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let’s trace a single client request.&lt;/p&gt;

&lt;p&gt;Step 1: Request Hits Nginx&lt;/p&gt;

&lt;p&gt;The client is unaware of Blue or Green.&lt;/p&gt;

&lt;p&gt;Step 2: Nginx Proxies to Blue&lt;/p&gt;

&lt;p&gt;Blue is the primary upstream.&lt;/p&gt;

&lt;p&gt;Step 3: Blue Fails&lt;/p&gt;

&lt;p&gt;Blue returns a 5xx or times out.&lt;/p&gt;

&lt;p&gt;Step 4: Nginx Intercepts the Failure&lt;/p&gt;

&lt;p&gt;Because of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;proxy_next_upstream error &lt;span class="nb"&gt;timeout &lt;/span&gt;http_5xx&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nginx does not forward the failure to the client.&lt;/p&gt;

&lt;p&gt;Step 5: Immediate Retry to Green&lt;/p&gt;

&lt;p&gt;Within the same client request, Nginx retries the request to Green.&lt;/p&gt;

&lt;p&gt;Step 6: Green Responds Successfully&lt;/p&gt;

&lt;p&gt;Green returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP 200
X-App-Pool: green
X-Release-Id: &amp;lt;RELEASE_ID_GREEN&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;br&gt;
The client sees HTTP 200, even though Blue failed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Proxy Buffering Matters&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;proxy_buffering on&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures Nginx does not stream partial responses.&lt;br&gt;
If Blue fails mid-request, Nginx can safely retry Green without exposing errors to clients.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Header Preservation&lt;/strong&gt;&lt;br&gt;
Each application response includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;X-App-Pool&lt;/li&gt;
&lt;li&gt;X-Release-Id
Nginx forwards these headers unchanged:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;proxy_pass_header X-App-Pool&lt;span class="p"&gt;;&lt;/span&gt;
proxy_pass_header X-Release-Id&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


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

&lt;p&gt;CI validation&lt;/p&gt;

&lt;p&gt;Runtime verification&lt;/p&gt;

&lt;p&gt;Clear observability of which pool served the request&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual Blue/Green Switching&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Traffic switching is handled by configuration templating.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Entrypoint Script&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;envsubst &lt;span class="s1"&gt;'$ACTIVE_POOL $PORT $RELEASE_ID_BLUE $RELEASE_ID_GREEN'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt; default.conf.template &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; default.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Changing ACTIVE_POOL=green&lt;/li&gt;
&lt;li&gt;Regenerating the Nginx config&lt;/li&gt;
&lt;li&gt;Reloading Nginx without downtime
No containers are restarted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stability Under Sustained Failure&lt;/strong&gt;&lt;br&gt;
During a ~10 second request loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero non-200 responses&lt;/li&gt;
&lt;li&gt;≥95% responses from Green&lt;/li&gt;
&lt;li&gt;Blue remains isolated until healthy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This satisfies all grader stability requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This project demonstrates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blue/Green deployment without Kubernetes&lt;/li&gt;
&lt;li&gt;Auto-failover within a single HTTP request&lt;/li&gt;
&lt;li&gt;Resilience implemented at the proxy layer&lt;/li&gt;
&lt;li&gt;Environment-driven infrastructure design&lt;/li&gt;
&lt;li&gt;Production-grade reliability using simple tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;High availability is not about avoiding failure—it’s about handling failure correctly.&lt;/p&gt;

&lt;p&gt;By combining Nginx upstreams, Docker Compose, and strict timeout and retry controls, we achieve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero downtime&lt;/li&gt;
&lt;li&gt;Safe rollbacks&lt;/li&gt;
&lt;li&gt;Transparent failover&lt;/li&gt;
&lt;li&gt;CI-ready verification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach mirrors real production systems and is an excellent foundation for any DevOps engineer.&lt;/p&gt;

&lt;p&gt;If you’re learning DevOps, mastering patterns like this matters far more than chasing tools. Reliability is a design choice.&lt;br&gt;
&lt;a href="https://github.com/KellsCodes/Blue-Green-with-Nginx-Upstreams" rel="noopener noreferrer"&gt;Explore the code here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>devops</category>
    </item>
    <item>
      <title>Automating Dockerized App Deployment with a Bash Script</title>
      <dc:creator>Ifeanyi Nworji</dc:creator>
      <pubDate>Sat, 13 Dec 2025 06:06:16 +0000</pubDate>
      <link>https://dev.to/ifeanyi_nworji/automating-dockerized-app-deployment-with-a-bash-script-41im</link>
      <guid>https://dev.to/ifeanyi_nworji/automating-dockerized-app-deployment-with-a-bash-script-41im</guid>
      <description>&lt;p&gt;DevOps is all about automation, reliability, and efficiency. In Stage 1 of my DevOps internship, I built a robust Bash script to automate the deployment of a Dockerized application to a remote Linux server.&lt;/p&gt;

&lt;p&gt;Whether you’re a developer, system administrator, or DevOps enthusiast, this guide will walk you step by step through creating a production-ready deployment script that handles everything from cloning a repository to configuring Nginx as a reverse proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Automate Deployment?
&lt;/h2&gt;

&lt;p&gt;Manual deployment is error-prone, inconsistent, and time-consuming. Automation provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consistency: Ensures deployments are identical every time.&lt;/li&gt;
&lt;li&gt;Speed: Reduces human intervention and saves time.&lt;/li&gt;
&lt;li&gt;Reliability: Detects errors early and logs them for troubleshooting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of this guide, you’ll have a single Bash script (deploy.sh) capable of fully deploying your Dockerized app to a remote server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Collect Parameters from User Input&lt;/strong&gt;&lt;br&gt;
The first step is interactively collecting essential details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git repository URL&lt;/li&gt;
&lt;li&gt;Personal Access Token (PAT)&lt;/li&gt;
&lt;li&gt;Branch name (defaults to main)&lt;/li&gt;
&lt;li&gt;SSH credentials (username, server IP, key path)&lt;/li&gt;
&lt;li&gt;Application port&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also validate the inputs to prevent script failure later. Example snippet:&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;# Git Repository URL&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GIT_REPO_URL&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-rp&lt;/span&gt; &lt;span class="s2"&gt;"Enter the git repository URL: "&lt;/span&gt; GIT_REPO_URL
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GIT_REPO_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; log_error &lt;span class="s2"&gt;"Repository URL cannot be empty."&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# Personal Access Token (PAT) - hidden input&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GIT_PAT&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-rsp&lt;/span&gt; &lt;span class="s2"&gt;"Enter your Git Personal Access Token (PAT): "&lt;/span&gt; GIT_PAT
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
  &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GIT_PAT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; log_error &lt;span class="s2"&gt;"Personal Access Token cannot be empty."&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# Branch name (default = main)&lt;/span&gt;
&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-rp&lt;/span&gt; &lt;span class="s2"&gt;"Enter branch name [default: main]: "&lt;/span&gt; GIT_BRANCH
&lt;span class="nv"&gt;GIT_BRANCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GIT_BRANCH&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;#SSH username&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SSH_USER&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-rp&lt;/span&gt; &lt;span class="s2"&gt;"Enter remote server SSH username: "&lt;/span&gt; SSH_USER
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; log_error &lt;span class="s2"&gt;"SSH username cannot be empty."&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;#Server IP address&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVER_IP&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-rp&lt;/span&gt; &lt;span class="s2"&gt;"Enter remote server IP address: "&lt;/span&gt; SERVER_IP
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SERVER_IP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ ^&lt;span class="o"&gt;([&lt;/span&gt;0-9]&lt;span class="o"&gt;{&lt;/span&gt;1,3&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="o"&gt;){&lt;/span&gt;3&lt;span class="o"&gt;}[&lt;/span&gt;0-9]&lt;span class="o"&gt;{&lt;/span&gt;1,3&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;log_error &lt;span class="s2"&gt;"Invalid IP format. Please enter a valid IPV4 address."&lt;/span&gt;
        &lt;span class="nv"&gt;SERVER_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;fi
done&lt;/span&gt;

&lt;span class="c"&gt;# SSH key path&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SSH_KEY_PATH&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-rp&lt;/span&gt; &lt;span class="s2"&gt;"Enter path to SSH private key: "&lt;/span&gt; SSH_KEY_PATH
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_KEY_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;log_error &lt;span class="s2"&gt;"SSH key file not found at &lt;/span&gt;&lt;span class="nv"&gt;$SSH_KEY_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nv"&gt;SSH_KEY_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;fi
done&lt;/span&gt;

&lt;span class="c"&gt;# Application port (internal container port)&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APP_PORT&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-rp&lt;/span&gt; &lt;span class="s2"&gt;"Enter internal application (container) port (e.g., 8080): "&lt;/span&gt; APP_PORT
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APP_PORT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ ^[0-9]+&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;log_error &lt;span class="s2"&gt;"Invalid port. Please enter a numeric value."&lt;/span&gt;
        &lt;span class="nv"&gt;APP_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;fi
done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Always validate user input using conditionals to ensure URLs are valid and files exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Clone or Pull the Repository&lt;/strong&gt;&lt;br&gt;
Next, we authenticate using the PAT and clone the repo, or pull the latest changes if it already exists:&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="nv"&gt;REPO_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; .git &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GIT_REPO_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;WORK_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/deployment/&lt;/span&gt;&lt;span class="nv"&gt;$REPO_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Create deployment directory if not exists&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WORK_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Authenticated URL (PAT safely embedded)&lt;/span&gt;
&lt;span class="nv"&gt;GIT_USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GIT_REPO_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;/:] &lt;span class="s1"&gt;'{print $(NF-1)}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;AUTH_REPO_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GIT_REPO_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s2"&gt;"s#https://#https://&lt;/span&gt;&lt;span class="nv"&gt;$GIT_USERNAME&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$GIT_PAT&lt;/span&gt;&lt;span class="s2"&gt;@#"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WORK_DIR&lt;/span&gt;&lt;span class="s2"&gt;/.git"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;log_info &lt;span class="s2"&gt;" Repository already exists. Pulling latest changes..."&lt;/span&gt;
    &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WORK_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    git reset &lt;span class="nt"&gt;--hard&lt;/span&gt;
    git clean &lt;span class="nt"&gt;-fd&lt;/span&gt;
    git fetch origin &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GIT_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    git checkout &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GIT_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    git pull origin &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GIT_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        log_error &lt;span class="s2"&gt;" Failed to pull latest changes from &lt;/span&gt;&lt;span class="nv"&gt;$GIT_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$EXIT_UNKNOWN&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;log_info &lt;span class="s2"&gt;" Cloning repository into &lt;/span&gt;&lt;span class="nv"&gt;$WORK_DIR&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;
    git clone &lt;span class="nt"&gt;--branch&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GIT_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AUTH_REPO_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WORK_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        log_error &lt;span class="s2"&gt;" Failed to clone repository. Please check your URL or PAT"&lt;/span&gt;
        &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$EXIT_UNKNOWN&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WORK_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;log_success &lt;span class="s2"&gt;" Repository is ready at: &lt;/span&gt;&lt;span class="nv"&gt;$WORK_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step ensures your local project folder is always up-to-date with the remote repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Navigate into the Project Directory&lt;/strong&gt;&lt;br&gt;
After cloning, we enter the project folder and validate the Docker setup:&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;# Check for Docker configuration  files&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"Dockerfile"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;log_success &lt;span class="s2"&gt;" Found Dockerfile - ready for Docker build."&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"compose.yaml"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"compose.yml"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"docker-compose.yaml"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"docker-compose.yml"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;log_success &lt;span class="s2"&gt;" Found docker-compose.yml - ready for multi-service deployment"&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;log_error &lt;span class="s2"&gt;" No Dockerfile or docker-compose.yml found. Cannot continue deployment."&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$EXIT_UNKNOWN&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: SSH into the Remote Server&lt;/strong&gt;&lt;br&gt;
Using SSH, we perform connectivity checks and prepare to execute remote commands:&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;# Validate SSH key exists&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_KEY_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;log_error &lt;span class="s2"&gt;"SSH key not found at: &lt;/span&gt;&lt;span class="nv"&gt;$SSH_KEY_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$EXIT_SSH_FAIL&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Test SSH connection (non-interactive)&lt;/span&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_KEY_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;StrictHostKeyChecking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;BatchMode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_USER&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$SERVER_IP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"echo 'SSH connection successful!'"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;log_error &lt;span class="s2"&gt;"Unable to connect to remote server via SSH. Please verify credentials, key permissions, and IP address."&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$EXIT_SSH_FAIL&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;log_success &lt;span class="s2"&gt;"SSH connection verified successfully."&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5: Prepare the Remote Environment&lt;/strong&gt;&lt;br&gt;
On the remote server, we need to ensure all dependencies exist:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Update packages&lt;/li&gt;
&lt;li&gt;Install Docker, Docker Compose, and Nginx&lt;/li&gt;
&lt;li&gt;Add user to Docker group&lt;/li&gt;
&lt;li&gt;Enable and start services
&lt;/li&gt;
&lt;/ol&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; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_KEY_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;StrictHostKeyChecking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_USER&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$SERVER_IP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; bash &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
set -e

echo "Updating system packages..."
sudo apt-get update -y &amp;amp;&amp;amp; sudo apt-get upgrade -y

echo "Installing required packages (curl, ca-certificates, gnupg, lsb-release)..."
sudo apt-get install -y ca-certificates curl gnupg lsb-release

# --- Install Docker if not installed ---
if ! command -v docker &amp;amp;&amp;gt;/dev/null; then
    echo "Docker not found. Installing Docker..."
    curl -fsSL https://get.docker.com | sudo bash
else
    echo "Docker already installed."
fi

# --- Install Docker Compose if not installed ---
if ! command -v docker-compose &amp;amp;&amp;gt;/dev/null; then
    echo "Installing Docker Compose..."
    sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;(uname -s)-&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;(uname -m)" -o /usr/local/bin/docker-compose
    sudo chmod +x /usr/local/bin/docker-compose
else
    echo "Docker Compose already installed."
fi

# --- Install nginx if not installed ---
if ! command -v nginx &amp;amp;&amp;gt;/dev/null; then
    echo "Installing nginx..."
    sudo apt-get install -y nginx
else
    echo "nginx already installed."
fi

# --- Add SSH user to docker group ---
if ! groups &lt;/span&gt;&lt;span class="nv"&gt;$SSH_USER&lt;/span&gt;&lt;span class="sh"&gt; | grep -q docker; then
    echo "Adding user '&lt;/span&gt;&lt;span class="nv"&gt;$SSH_USER&lt;/span&gt;&lt;span class="sh"&gt;' to docker group..."
    sudo usermod -aG docker &lt;/span&gt;&lt;span class="nv"&gt;$SSH_USER&lt;/span&gt;&lt;span class="sh"&gt;
    echo "You may need to log out and back in for this to take effect."
else
    echo "User '&lt;/span&gt;&lt;span class="nv"&gt;$SSH_USER&lt;/span&gt;&lt;span class="sh"&gt;' already in docker group."
fi

# --- Enable and start services ---
sudo systemctl enable docker
sudo systemctl start docker
sudo systemctl enable nginx
sudo systemctl start nginx

# --- Confirm installation versions ---
echo "Confirming versions..."
docker --version
docker-compose --version
nginx -v

echo "Remote environment setup complete."
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Best practice:&lt;/strong&gt; Always confirm installation versions and service status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nt"&gt;--version&lt;/span&gt;
docker-compose &lt;span class="nt"&gt;--version&lt;/span&gt;
nginx &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 6: Deploy the Dockerized Application&lt;/strong&gt;&lt;br&gt;
We transfer project files to the server and build/run Docker containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rsync &lt;span class="nt"&gt;-avz&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"ssh -i &lt;/span&gt;&lt;span class="nv"&gt;$SSH_KEY_PATH&lt;/span&gt;&lt;span class="s2"&gt; -o StrictHostKeyChecking=no"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--exclude&lt;/span&gt; &lt;span class="s1"&gt;'.git'&lt;/span&gt; &lt;span class="nt"&gt;--exclude&lt;/span&gt; &lt;span class="s1"&gt;'node_modules'&lt;/span&gt; &lt;span class="nt"&gt;--exclude&lt;/span&gt; &lt;span class="s1"&gt;'.env'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CLONE_DIR&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_USER&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$SERVER_IP&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$REMOTE_APP_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_KEY_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;StrictHostKeyChecking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_USER&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$SERVER_IP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; bash &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
set -e

cd &lt;/span&gt;&lt;span class="nv"&gt;$REMOTE_APP_DIR&lt;/span&gt;&lt;span class="sh"&gt;

# --- Detect Docker configuration ---
if [[ -f "docker-compose.yml" ]]; then
    echo "docker-compose.yml found. Starting with Docker Compose..."
    sudo docker-compose pull
    sudo docker-compose build
    sudo docker-compose up -d
elif [[ -f "Dockerfile" ]]; then
    echo "Dockerfile found. Building and running manually..."
    APP_NAME=&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;(basename &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;(pwd))
    sudo docker build -t &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;APP_NAME .
    sudo docker run -d -p &lt;/span&gt;&lt;span class="nv"&gt;$APP_PORT&lt;/span&gt;&lt;span class="sh"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$APP_PORT&lt;/span&gt;&lt;span class="sh"&gt; --name &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;APP_NAME &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;APP_NAME
else
    echo "No Dockerfile or docker-compose.yml found in project directory."
    exit 1
fi

# --- Step 3: Validate container health ---
echo "Checking running containers..."
sudo docker ps

# --- Step 4: Verify app is accessible on the specified port ---
echo "Validating application accessibility on port &lt;/span&gt;&lt;span class="nv"&gt;$APP_PORT&lt;/span&gt;&lt;span class="sh"&gt;..."
sleep 5
if curl -s "http://localhost:&lt;/span&gt;&lt;span class="nv"&gt;$APP_PORT&lt;/span&gt;&lt;span class="sh"&gt;" &amp;gt;/dev/null; then
    echo "Application is running and accessible on port &lt;/span&gt;&lt;span class="nv"&gt;$APP_PORT&lt;/span&gt;&lt;span class="sh"&gt;!"
else
    echo "Application did not respond on port &lt;/span&gt;&lt;span class="nv"&gt;$APP_PORT&lt;/span&gt;&lt;span class="sh"&gt;. Check container logs."
    sudo docker logs &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;(sudo docker ps -q --latest)
fi
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures the container is up, healthy, and accessible on the specified port.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7: Configure Nginx as a Reverse Proxy&lt;/strong&gt;&lt;br&gt;
Nginx forwards traffic from port 80 to your Docker container:&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="nv"&gt;DOMAIN_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOMAIN_NAME&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.com&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Create a temporary nginx config locally with correct escaping for nginx $-vars&lt;/span&gt;
&lt;span class="nv"&gt;TMP_NGINX_CONF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt; /tmp/app_proxy.XXXXXX.conf&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP_NGINX_CONF&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
server {
    listen 80;
    server_name &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOMAIN_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;;

    location / {
        proxy_pass http://127.0.0.1:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APP_PORT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;;
        proxy_http_version 1.1;
        proxy_set_header Upgrade &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;host;
        proxy_set_header X-Real-IP &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;remote_addr;
        proxy_set_header X-Forwarded-For &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;scheme;
        proxy_cache_bypass &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;http_upgrade;
    }

    access_log /var/log/nginx/app_access.log;
    error_log /var/log/nginx/app_error.log;
}

# SSL placeholder (Certbot or self-signed)
# server {
#     listen 443 ssl;
#     server_name &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOMAIN_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;;
#     ssl_certificate /etc/ssl/certs/app.crt;
#     ssl_certificate_key /etc/ssl/private/app.key;
#     location / {
#         proxy_pass http://127.0.0.1:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APP_PORT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;;
#         proxy_set_header Host &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;host;
#     }
# }
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;NGINX_CONFIG_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/etc/nginx/sites-available/app_proxy"&lt;/span&gt;
&lt;span class="nv"&gt;NGINX_ENABLED_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/etc/nginx/sites-enabled/app_proxy"&lt;/span&gt;

&lt;span class="c"&gt;# Copy config to remote /tmp then move with sudo to proper location to avoid permission issues&lt;/span&gt;
scp &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_KEY_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP_NGINX_CONF&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_USER&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$SERVER_IP&lt;/span&gt;&lt;span class="s2"&gt;:/tmp/app_proxy.conf"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  log_error &lt;span class="s2"&gt;"Failed to upload nginx config to remote host."&lt;/span&gt;
  &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP_NGINX_CONF&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$EXIT_NGINX_FAIL&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP_NGINX_CONF&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Apply config remotely: install nginx if missing, remove default, move config, enable, test, reload&lt;/span&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_KEY_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;StrictHostKeyChecking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_USER&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$SERVER_IP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; bash &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;REMOTE_EOF&lt;/span&gt;&lt;span class="sh"&gt;'
set -e

NGINX_CONFIG_PATH="/etc/nginx/sites-available/app_proxy"
NGINX_ENABLED_PATH="/etc/nginx/sites-enabled/app_proxy"

# Install nginx if missing
if ! command -v nginx &amp;amp;&amp;gt;/dev/null; then
  echo "Installing nginx..."
  sudo apt-get update -y
  sudo apt-get install -y nginx
fi

# Remove default site if present
if [ -f /etc/nginx/sites-enabled/default ]; then
  echo "Removing default nginx site..."
  sudo rm -f /etc/nginx/sites-enabled/default
fi

# Move uploaded config into place
sudo mv /tmp/app_proxy.conf "&lt;/span&gt;&lt;span class="nv"&gt;$NGINX_CONFIG_PATH&lt;/span&gt;&lt;span class="sh"&gt;"
sudo chown root:root "&lt;/span&gt;&lt;span class="nv"&gt;$NGINX_CONFIG_PATH&lt;/span&gt;&lt;span class="sh"&gt;"
sudo chmod 644 "&lt;/span&gt;&lt;span class="nv"&gt;$NGINX_CONFIG_PATH&lt;/span&gt;&lt;span class="sh"&gt;"

# Enable site (idempotent)
sudo ln -sf "&lt;/span&gt;&lt;span class="nv"&gt;$NGINX_CONFIG_PATH&lt;/span&gt;&lt;span class="sh"&gt;" "&lt;/span&gt;&lt;span class="nv"&gt;$NGINX_ENABLED_PATH&lt;/span&gt;&lt;span class="sh"&gt;"

# Test and reload
if sudo nginx -t; then
  echo "nginx config OK - reloading"
  sudo systemctl reload nginx
else
  echo "nginx config test FAILED"
  sudo nginx -t || true
  exit 1
fi

echo "Nginx reverse proxy configured"
&lt;/span&gt;&lt;span class="no"&gt;REMOTE_EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 8: Validate Deployment&lt;/strong&gt;&lt;br&gt;
Check that everything is running and accessible:&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; &lt;span class="nv"&gt;$SSH_KEY&lt;/span&gt; &lt;span class="nv"&gt;$REMOTE_USER&lt;/span&gt;@&lt;span class="nv"&gt;$REMOTE_IP&lt;/span&gt; &lt;span class="s2"&gt;"docker ps"&lt;/span&gt;
curl http://&lt;span class="nv"&gt;$REMOTE_IP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All services should respond correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 9: Logging and Error Handling&lt;/strong&gt;&lt;br&gt;
Logging is critical for troubleshooting. Our script:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Saves logs to deploy_YYYYMMDD.log&lt;/li&gt;
&lt;li&gt;Uses trap to catch errors&lt;/li&gt;
&lt;li&gt;Exits with meaningful codes
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;LOG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_DIR&lt;/span&gt;&lt;span class="s2"&gt;/deploy_&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +&lt;span class="s1"&gt;'%Y%m%d_%H%M%S'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.log"&lt;/span&gt;

&lt;span class="c"&gt;# --- Logging functions ---&lt;/span&gt;
log_info&lt;span class="o"&gt;()&lt;/span&gt;    &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="s2"&gt;33[1;34m[INFO]&lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="s2"&gt;33[0m &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;    | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
log_success&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="s2"&gt;33[1;32m[SUCCESS]&lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="s2"&gt;33[0m &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
log_warn&lt;span class="o"&gt;()&lt;/span&gt;    &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="s2"&gt;33[1;33m[WARN]&lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="s2"&gt;33[0m &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;    | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
log_error&lt;span class="o"&gt;()&lt;/span&gt;   &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="s2"&gt;33[1;31m[ERROR]&lt;/span&gt;&lt;span class="se"&gt;\0&lt;/span&gt;&lt;span class="s2"&gt;33[0m &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;   | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# --- Error trapping ---&lt;/span&gt;
&lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s1"&gt;'handle_error $? $LINENO'&lt;/span&gt; ERR
handle_error&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;exit_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;line_no&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;
    log_error &lt;span class="s2"&gt;" Script failed at line &lt;/span&gt;&lt;span class="nv"&gt;$line_no&lt;/span&gt;&lt;span class="s2"&gt; (exit code: &lt;/span&gt;&lt;span class="nv"&gt;$exit_code&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
    log_error &lt;span class="s2"&gt;" Deployment aborted. Check &lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt; for details."&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$exit_code&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 10: Idempotency and Cleanup&lt;/strong&gt;&lt;br&gt;
The script is safe to rerun. Previous containers are stopped/removed before redeployment. Optional --cleanup removes all deployed resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By building this Automated Deployment Bash Script, I’ve learned how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Interact with users in Bash scripts&lt;/li&gt;
&lt;li&gt;Automate Docker deployment&lt;/li&gt;
&lt;li&gt;Configure remote servers and Nginx&lt;/li&gt;
&lt;li&gt;Implement robust logging, error handling, and idempotency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This project mirrors real-world DevOps workflows and is a great starting point for anyone looking to build production-grade deployment scripts.&lt;br&gt;
&lt;a href="https://github.com/KellsCodes/automated-deploy" rel="noopener noreferrer"&gt;Complete repo link&lt;/a&gt;&lt;/p&gt;

</description>
      <category>bash</category>
      <category>devops</category>
      <category>tutorial</category>
      <category>docker</category>
    </item>
    <item>
      <title>Building and Testing a Mini VPC with Python and Linux Namespaces</title>
      <dc:creator>Ifeanyi Nworji</dc:creator>
      <pubDate>Wed, 12 Nov 2025 14:25:50 +0000</pubDate>
      <link>https://dev.to/ifeanyi_nworji/building-and-testing-a-mini-vpc-with-python-and-linux-namespaces-5cg9</link>
      <guid>https://dev.to/ifeanyi_nworji/building-and-testing-a-mini-vpc-with-python-and-linux-namespaces-5cg9</guid>
      <description>&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;&lt;br&gt;
In this project, I built a Mini Virtual Private Cloud (VPC) system on Linux using nothing but Python and native networking tools.&lt;br&gt;
It mimics real AWS networking — with public/private subnets, NAT, VPC peering, and firewall policies — but all runs locally.&lt;/p&gt;

&lt;p&gt;This setup is perfect for DevOps learners and cloud enthusiasts who want to see how networks actually work behind the scenes.&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%2F1z62xaiq1waizc8uvhui.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%2F1z62xaiq1waizc8uvhui.png" alt=" " width="701" height="621"&gt;&lt;/a&gt;&lt;br&gt;
fig.1 VPC network diagram&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Bridge (br0) → acts like your VPC switch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Namespaces → represent isolated networks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;veth pairs → connect subnets to bridge&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;iptables NAT → allows outbound access only from the public subnet&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Setup&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Create the VPC&lt;/strong&gt;&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;python3 vpcctl.py create-vpc vpc1 10.10.0.0/16 &lt;span class="nt"&gt;--public-interface&lt;/span&gt; wlp2s0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a virtual private cloud (VPC) simulation with the name vpc1 and a designated IP address range, linked to your actual public network interface.&lt;br&gt;
10.10.0.0/16: This is the IP address range (specifically, a CIDR block) that the VPC will use for its internal network. The /16 denotes the specific size of the network, allowing for over 65,000 internal IP addresses within the VPC.&lt;/p&gt;

&lt;p&gt;--public-interface wlp2s0: This flag tells the script to connect the simulated VPC to your actual physical network interface named wlp2s0(your system can have a different interface, run this command to see yours: ip addr show or ifconfig). This allows the resources inside your simulated VPC to communicate with the outside world (e.g., the internet) via your computer's real connection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Add Subnets&lt;/strong&gt;&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;python3 vpcctl.py add-subnet vpc1 public &lt;span class="nt"&gt;--type&lt;/span&gt; public &lt;span class="nt"&gt;--base-cidr&lt;/span&gt; 10.10.0.0/16

&lt;span class="nb"&gt;sudo &lt;/span&gt;python3 vpcctl.py add-subnet vpc1 private &lt;span class="nt"&gt;--type&lt;/span&gt; private &lt;span class="nt"&gt;--base-cidr&lt;/span&gt; 10.10.0.0/16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;vpc1-public → 10.10.1.0/24 (Internet access)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;vpc1-private → 10.10.2.0/24 (Internal only)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Deploy Demo Applications&lt;/strong&gt;&lt;br&gt;
Run a web app in 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-public python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From your host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl 10.10.1.2:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the directory listing&lt;/p&gt;

&lt;p&gt;Run a web app in 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-private python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl 10.10.2.2:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll get no response — because private subnets aren’t exposed externally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Validate Connectivity&lt;/strong&gt;&lt;br&gt;
Communication within the same VPC&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-private ping &lt;span class="nt"&gt;-c&lt;/span&gt; 3 10.10.1.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works (internal VPC communication).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Internet access from public subnet&lt;/strong&gt;&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-public ping &lt;span class="nt"&gt;-c&lt;/span&gt; 3 8.8.8.8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works via NAT.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Internet access from private subnet&lt;/strong&gt;&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-private ping &lt;span class="nt"&gt;-c&lt;/span&gt; 2 8.8.8.8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Blocked — no default route to internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6: Test Multiple VPCs and Peering&lt;/strong&gt;&lt;br&gt;
Create two VPCs&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;python3 vpcctl.py create-vpc vpc2 10.20.0.0/16 &lt;span class="nt"&gt;--public-interface&lt;/span&gt; wlp2s0

&lt;span class="nb"&gt;sudo &lt;/span&gt;python3 vpcctl.py create-vpc vpc3 10.30.0.0/16 &lt;span class="nt"&gt;--public-interface&lt;/span&gt; wlp2s0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add subnets to VPC2 and VPC3&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;# PUBLIC SUBNET on vpc2&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;python3 vpcctl.py add-subnet vpc2 public &lt;span class="nt"&gt;--type&lt;/span&gt; public &lt;span class="nt"&gt;--base-cidr&lt;/span&gt; 10.20.0.0/16

&lt;span class="c"&gt;# PRIVATE SUBNET on vpc2&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;python3 vpcctl.py add-subnet vpc2 private &lt;span class="nt"&gt;--type&lt;/span&gt; private &lt;span class="nt"&gt;--base-cidr&lt;/span&gt; 10.20.0.0/16

&lt;span class="c"&gt;# PUBLIC SUBNET on vpc3&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;python3 vpcctl.py add-subnet vpc3 public &lt;span class="nt"&gt;--type&lt;/span&gt; public &lt;span class="nt"&gt;--base-cidr&lt;/span&gt; 10.30.0.0/16

&lt;span class="c"&gt;# PRIVATE SUBNET on vpc3&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;python3 vpcctl.py add-subnet vpc3 private &lt;span class="nt"&gt;--type&lt;/span&gt; private &lt;span class="nt"&gt;--base-cidr&lt;/span&gt; 10.30.0.0/16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check isolation&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-public ping &lt;span class="nt"&gt;-c&lt;/span&gt; 2 10.20.1.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Blocked — fully isolated by default.&lt;/p&gt;

&lt;p&gt;Peer them&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;python3 vpcctl.py peer-vpc vpc1 vpc2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now ping again:&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-public ping &lt;span class="nt"&gt;-c&lt;/span&gt; 2 10.20.1.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works (controlled communication after peering).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7: Apply Security Policies (Firewall)&lt;/strong&gt;&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;python3 vpcctl.py apply-policies vpc2 &lt;span class="nt"&gt;--policies&lt;/span&gt; policies.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Policies.json:&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="o"&gt;[&lt;/span&gt;
  &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"subnet"&lt;/span&gt;: &lt;span class="s2"&gt;"10.20.2.0/24"&lt;/span&gt;,
    &lt;span class="s2"&gt;"ingress"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
      &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"port"&lt;/span&gt;: 80, &lt;span class="s2"&gt;"protocol"&lt;/span&gt;: &lt;span class="s2"&gt;"tcp"&lt;/span&gt;, &lt;span class="s2"&gt;"action"&lt;/span&gt;: &lt;span class="s2"&gt;"allow"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,
      &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"port"&lt;/span&gt;: 22, &lt;span class="s2"&gt;"protocol"&lt;/span&gt;: &lt;span class="s2"&gt;"tcp"&lt;/span&gt;, &lt;span class="s2"&gt;"action"&lt;/span&gt;: &lt;span class="s2"&gt;"deny"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,
      &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"port"&lt;/span&gt;: 443, &lt;span class="s2"&gt;"protocol"&lt;/span&gt;: &lt;span class="s2"&gt;"tcp"&lt;/span&gt;, &lt;span class="s2"&gt;"action"&lt;/span&gt;: &lt;span class="s2"&gt;"allow"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;]&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;would automatically block SSH access while keeping web traffic open.&lt;/p&gt;

&lt;p&gt;Test policies:&lt;/p&gt;

&lt;p&gt;Start a simple HTTP server on port 80 in the target namespace (vpc2)&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-public python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test from the source namespace using nc:&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-private nc &lt;span class="nt"&gt;-vz&lt;/span&gt; 10.20.1.2 80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 8: Cleanup&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make cleanup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Removes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;All namespaces&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The bridge&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NAT/firewall rules&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ensures no residual configuration remains.&lt;/p&gt;

&lt;p&gt;To run this with just one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and clean up with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make cleanup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/KellsCodes/vpcctl-python.git" rel="noopener noreferrer"&gt;Github link&lt;/a&gt;&lt;/p&gt;

</description>
      <category>networking</category>
      <category>devops</category>
      <category>python</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
