<?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: Dimeji Ojewunmi</title>
    <description>The latest articles on DEV Community by Dimeji Ojewunmi (@dimeji_ojewunmi_5e27256).</description>
    <link>https://dev.to/dimeji_ojewunmi_5e27256</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1718620%2F3555c1b2-b39c-4867-bd99-93fb39e64d13.png</url>
      <title>DEV Community: Dimeji Ojewunmi</title>
      <link>https://dev.to/dimeji_ojewunmi_5e27256</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dimeji_ojewunmi_5e27256"/>
    <language>en</language>
    <item>
      <title>Migration &amp; Modernization From Azure to AWS</title>
      <dc:creator>Dimeji Ojewunmi</dc:creator>
      <pubDate>Wed, 21 Oct 2026 23:05:49 +0000</pubDate>
      <link>https://dev.to/dimeji_ojewunmi_5e27256/migration-modernization-from-azure-to-aws-97d</link>
      <guid>https://dev.to/dimeji_ojewunmi_5e27256/migration-modernization-from-azure-to-aws-97d</guid>
      <description>&lt;h1&gt;
  
  
  My Hands-On Migration of a Node.js App and PostgreSQL Database from Azure to AWS
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;My migration lab, from a blocked local network to PostgreSQL on RDS and a private application on EC2.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full load: 100%. Three tables validated. Zero mismatched records.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Those were the results on my AWS DMS dashboard. The data had made it from Azure to Amazon RDS, and the row counts agreed.&lt;/p&gt;

&lt;p&gt;There was still work hiding behind that success: sequence-backed IDs, defaults, constraints, and permissions needed attention before the target database was ready for the application.&lt;/p&gt;

&lt;p&gt;I built this lab to practice moving a Node.js order-management app and its PostgreSQL database to AWS. I used an Azure VM as the simulated on-premises source. There were only three tables, which made it easy to compare the data while I worked through the networking, database cutover, and application move.&lt;/p&gt;

&lt;p&gt;The database copy was one step. Getting the application to use it—and then making the application reachable through the load balancer—took more work.&lt;/p&gt;

&lt;h2&gt;
  
  
  I originally wanted to run this from home
&lt;/h2&gt;

&lt;p&gt;My original plan was to run the source locally and connect it to AWS. I ran into connectivity blockers with my MTN and Airtel 5G router setups before I could get to the migration itself.&lt;/p&gt;

&lt;p&gt;I still wanted to carry out the project, so I moved the source environment to an Azure VM. There, I could configure the private network and VPN connection myself.&lt;/p&gt;

&lt;p&gt;I kept the application and PostgreSQL on a private machine, with AWS on a separate network and a VPN between them. That let me practice the migration steps I had set out to learn.&lt;/p&gt;

&lt;p&gt;The MTN and Airtel 5G router limitations remained unresolved. Using Azure let me continue, and I ended up working through the network configuration in both clouds.&lt;/p&gt;

&lt;h2&gt;
  
  
  My lab setup
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Source (Azure, simulating on-prem):&lt;/strong&gt; an Ubuntu VM with a private address, a Node.js/Express order-management app on port 3000, and PostgreSQL 16 containing &lt;code&gt;customers&lt;/code&gt;, &lt;code&gt;products&lt;/code&gt;, and &lt;code&gt;orders&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target (AWS):&lt;/strong&gt; a VPC (&lt;code&gt;10.16.0.0/16&lt;/code&gt;), private Amazon RDS for PostgreSQL, AWS DMS, and a private EC2 instance running the application. An internet-facing Application Load Balancer (ALB) terminates HTTPS and forwards traffic to the private app on port 3000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Migration connectivity:&lt;/strong&gt; a route-based IPsec Site-to-Site VPN between Azure (&lt;code&gt;172.16.0.0/16&lt;/code&gt;) and AWS (&lt;code&gt;10.16.0.0/16&lt;/code&gt;). The Azure VPN connection showed &lt;strong&gt;Connected&lt;/strong&gt;; on AWS, one VPN tunnel was UP and the other DOWN. That was sufficient for the lab, but &lt;strong&gt;not&lt;/strong&gt; a highly available production VPN design.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Migration path (private traffic)
  Azure simulated on-prem                    AWS
  172.16.0.0/16                              10.16.0.0/16
  +----------------------+   IPsec VPN   +----------------------+
  | Ubuntu VM 172.16.0.4|===============&amp;gt;| DMS replication      |
  | Node.js :3000       |                | RDS PostgreSQL       |
  | PostgreSQL 16       |                | Private subnets      |
  +----------------------+                +----------------------+

                 Final application request path
  Browser --HTTPS :443--&amp;gt; Public ALB --HTTP :3000--&amp;gt; Private EC2
                                                   |
                                                   +--TCP :5432--&amp;gt; Private RDS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Security note:&lt;/strong&gt; This diagram shows the logical data flow, not every route table, gateway, or security-group rule. The public ALB is the entry point; the EC2 application and RDS database do not need public IPs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1 — Setting up the source in Azure
&lt;/h2&gt;

&lt;p&gt;The source began with an Azure virtual network, a private workload subnet, a VPN gateway subnet, and an Ubuntu VM hosting both the application and PostgreSQL. I gave the VM no public NIC address. That decision made connectivity part of the exercise from the beginning: every connection to the source needed a deliberate path.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5feh5xmqfwhuk9wp2tw0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5feh5xmqfwhuk9wp2tw0.png" alt="Azure Ubuntu VM showing private IP 172.16.0.4 and no public NIC IP" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: The Azure Ubuntu VM used as the simulated on-premises source. Its private IP is &lt;code&gt;172.16.0.4&lt;/code&gt;; the public IP shown lower on the page belongs to the NAT gateway.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F48l8pnr2l43xxbtxvcat.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F48l8pnr2l43xxbtxvcat.png" alt="Azure VNet subnets: private-subnet, GatewaySubnet, and AzureBastionSubnet" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2: The Azure VNet contains separate workload, VPN gateway, and Bastion subnets. The workload subnet is associated with &lt;code&gt;private-SG&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The database contained &lt;code&gt;customers&lt;/code&gt;, &lt;code&gt;products&lt;/code&gt;, and &lt;code&gt;orders&lt;/code&gt;. The application could read and write data and exposed a health endpoint. That gave the migration a practical acceptance test: after the move, could the application still use the database the way it did before?&lt;/p&gt;

&lt;p&gt;Copying three tables would be easy to observe. Preserving their behavior would take more care.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 2 — “Connected” was the first checkpoint
&lt;/h2&gt;

&lt;p&gt;Before DMS could move a row, the two private networks needed a working conversation.&lt;/p&gt;

&lt;p&gt;I configured compatible IPsec tunnel parameters on the Azure VPN gateway and AWS virtual private gateway. In Azure, a Local Network Gateway represented the AWS VPN endpoint and remote address space. In AWS, the private route tables needed a route back to &lt;code&gt;172.16.0.0/16&lt;/code&gt; through the virtual private gateway.&lt;/p&gt;

&lt;p&gt;Azure showed &lt;strong&gt;Connected&lt;/strong&gt;. The next step was to check what the AWS side could actually tell me.&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;Figure 3: The Azure VPN gateway uses the &lt;code&gt;VpnGw1AZ&lt;/code&gt; SKU and a route-based VPN. Its public IP is the Azure-side VPN endpoint.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbt6a3oyfj9cm3ofdkym6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbt6a3oyfj9cm3ofdkym6.png" alt="Azure Local Network Gateway representing the AWS VPN endpoint" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 4: The Azure Local Network Gateway represents the AWS tunnel endpoint and the remote AWS address space, &lt;code&gt;10.16.0.0/16&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy8qdwukzmyodih20ggtl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy8qdwukzmyodih20ggtl.png" alt="Azure VPN connection azure-to-aws-tunnel1 showing Connected" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 5: The Azure gateway connection list shows &lt;code&gt;azure-to-aws-tunnel1&lt;/code&gt; in the Connected state.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Follow the connection into AWS
&lt;/h3&gt;

&lt;p&gt;On AWS, I created &lt;code&gt;azure-onprem-cgw&lt;/code&gt; to represent the Azure VPN gateway's public IP, &lt;code&gt;4.255.22.3&lt;/code&gt;. I attached &lt;code&gt;azure-migration-vgw&lt;/code&gt; to &lt;code&gt;Demo-VPC&lt;/code&gt;, then linked the gateways through the &lt;code&gt;azure-to-aws-vpn&lt;/code&gt; Site-to-Site VPN connection.&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;Figure 6: The AWS customer gateway is Available and points to the Azure VPN gateway public IP. Although this page lists a BGP ASN, the VPN connection in this lab uses static routing.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;Figure 7: The AWS virtual private gateway is Available and its VPC attachment state is Attached.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The AWS tunnel view gave me the other half of the connectivity check: Tunnel 1 was Up, with outside IP &lt;code&gt;3.227.45.240&lt;/code&gt;, matching the endpoint configured in Azure's Local Network Gateway. Tunnel 2 was Down, so this lab had only one operational tunnel.&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;Figure 8: AWS reports one operational tunnel and explicitly warns that both tunnels are not in use.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The tunnel status was only one piece of the path. I checked the remote network in two places: the VPN connection's static routes and the VPC subnet's route table. The VPN static route listed the Azure address space, &lt;code&gt;172.16.0.0/16&lt;/code&gt;. The &lt;code&gt;priv-RT&lt;/code&gt; route table, associated with &lt;code&gt;priv-subnet&lt;/code&gt;, directed that same address space to the virtual private gateway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fomgvap6kpy83ta5da5jh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fomgvap6kpy83ta5da5jh.png" alt="AWS VPN static route for the Azure network" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 9: The Site-to-Site VPN uses static routing and lists &lt;code&gt;172.16.0.0/16&lt;/code&gt; as an Available remote prefix.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F04munkestapnr4hy7bs5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F04munkestapnr4hy7bs5.png" alt="AWS private subnet route table with an active route to Azure" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 10: The Azure route, &lt;code&gt;172.16.0.0/16&lt;/code&gt;, is Active and targets the virtual private gateway. A separate route to &lt;code&gt;10.200.0.0/24&lt;/code&gt; is Blackhole; it does not establish connectivity for that prefix and is distinct from the Azure route.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Give PostgreSQL a path through the firewall
&lt;/h3&gt;

&lt;p&gt;The Azure network security group allowed TCP &lt;code&gt;5432&lt;/code&gt; from the AWS VPC range, &lt;code&gt;10.16.0.0/16&lt;/code&gt;, to the source VM, &lt;code&gt;172.16.0.4&lt;/code&gt;. This captured the source-side database rule used for replication.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl6ck5ixyzk66zz4v4eu8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl6ck5ixyzk66zz4v4eu8.png" alt="Azure NSG rule allowing PostgreSQL traffic from the AWS VPC" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 11: The Azure inbound rule allows PostgreSQL traffic from &lt;code&gt;10.16.0.0/16&lt;/code&gt; to &lt;code&gt;172.16.0.4&lt;/code&gt; on TCP port &lt;code&gt;5432&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At this point, &lt;strong&gt;Connected&lt;/strong&gt; had become a useful starting point for investigation. I still needed routes in both directions and firewall rules that permitted the actual traffic. Replication needed PostgreSQL on TCP &lt;strong&gt;5432&lt;/strong&gt;. The application would later need its own path on &lt;strong&gt;3000&lt;/strong&gt;—a distinction that would matter again when I reached the load balancer.&lt;/p&gt;

&lt;p&gt;I also treated the single operational tunnel as a lab limitation. A production design should validate redundant tunnels, failover behavior, monitoring, and appropriate routing resilience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 3 — Connecting DMS to the source and target
&lt;/h2&gt;

&lt;p&gt;With the private network path established, I provisioned private Amazon RDS for PostgreSQL and set up AWS Database Migration Service (DMS) endpoints for the Azure source and AWS target. The source was PostgreSQL 16 and the RDS target reported PostgreSQL 18.3, so this exercise also required attention to engine-version differences and schema compatibility.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fynijadp7l0pgve7370n4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fynijadp7l0pgve7370n4.png" alt="AWS DMS source endpoint for PostgreSQL at 172.16.0.4" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 12: The DMS source endpoint connects to &lt;code&gt;migrationdb&lt;/code&gt; on the Azure VM using &lt;code&gt;dms_user&lt;/code&gt; and port &lt;code&gt;5432&lt;/code&gt;. The screenshot shows SSL mode None.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fevtvf4o6tdro4ebe870y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fevtvf4o6tdro4ebe870y.png" alt="AWS DMS target endpoint for Amazon RDS PostgreSQL" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 13: The DMS target endpoint connects to &lt;code&gt;migrationdb&lt;/code&gt; on Amazon RDS using port &lt;code&gt;5432&lt;/code&gt;, with SSL mode Require.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify connectivity from the DMS replication instance
&lt;/h3&gt;

&lt;p&gt;The DMS replication instance, &lt;code&gt;azure-postgres-migration&lt;/code&gt;, had private IP &lt;code&gt;10.16.2.71&lt;/code&gt; in the AWS VPC and no public IP. I tested each database endpoint from that instance to verify the connection used by the migration service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7wtfh9s2nxz5i0ia6dnx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7wtfh9s2nxz5i0ia6dnx.png" alt="AWS DMS replication instance with private IP and no public IP" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 14: The replication instance is Available, uses private IP &lt;code&gt;10.16.2.71&lt;/code&gt;, and lists its VPC, subnet group, and security group.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv9ymcahqi8sgy4d0tuin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv9ymcahqi8sgy4d0tuin.png" alt="Successful DMS connection test to the Azure PostgreSQL source" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 15: The source endpoint connection test from &lt;code&gt;azure-postgres-migration&lt;/code&gt; is Successful, providing a database connectivity check across the private migration path.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fws8kyrcqyhw20niy2cll.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fws8kyrcqyhw20niy2cll.png" alt="Successful DMS connection test to the RDS PostgreSQL target" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 16: The target endpoint connection test from the same replication instance is Successful. These tests establish endpoint access at the time of testing; migration and data validation are checked separately.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Both endpoint tests returned &lt;strong&gt;Successful&lt;/strong&gt;. That gave me evidence that the replication instance could connect to each database.&lt;/p&gt;

&lt;p&gt;I chose &lt;strong&gt;full load plus change data capture (CDC)&lt;/strong&gt;: copy the existing rows, then apply changes made while the source remained in use. The initial copy would get the target populated; CDC would keep changes moving until the cutover boundary.&lt;/p&gt;

&lt;p&gt;I organized the migration around these checkpoints:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Establish and test private connectivity and database access.&lt;/li&gt;
&lt;li&gt;Create and test DMS source and target endpoints.&lt;/li&gt;
&lt;li&gt;Run the pre-migration assessment and review warnings.&lt;/li&gt;
&lt;li&gt;Run full load and CDC, monitoring task and table status.&lt;/li&gt;
&lt;li&gt;Compare data and validate application read/write behavior.&lt;/li&gt;
&lt;li&gt;Freeze source writes, confirm target consistency, and stop replication.&lt;/li&gt;
&lt;li&gt;Reconfigure and move the application to AWS, then verify end-to-end access.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Phase 4 — The dashboard reached 100%
&lt;/h2&gt;

&lt;p&gt;Before relying on the task results, I checked the pre-migration assessment. Its summary had a little more nuance than a simple pass or fail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3z6zd2cg43rp1mp64s6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3z6zd2cg43rp1mp64s6.png" alt="AWS DMS assessment showing 26 passed, 2 warnings, 15 skipped, and 0 failed" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 17: The assessment summary reports 26 passed checks, 2 warnings, 15 skipped checks, and 0 failures.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The assessment reported &lt;strong&gt;43 checks: 26 passed, 2 warnings, 15 skipped, and 0 failed&lt;/strong&gt;. The warnings and skipped checks still called for manual review.&lt;/p&gt;

&lt;p&gt;Then came the encouraging result: full load reached &lt;strong&gt;100%&lt;/strong&gt;, with replication ongoing. The table statistics and direct SQL comparisons gave me two more ways to inspect what had arrived.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy72xevac83vfo9zeedbu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy72xevac83vfo9zeedbu.png" alt="AWS DMS full load at 100 percent with replication ongoing" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 18: The DMS task shows “Load completed, replication ongoing”: full load has reached 100%, and CDC is still running.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvarhypgl4f2czhdc7woi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvarhypgl4f2czhdc7woi.png" alt="AWS DMS table statistics showing three completed and validated tables" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 19: DMS reports three completed tables and three validated tables, with zero mismatched records, validation errors, or pending records.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3hmrbpffzrzsmz713jw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3hmrbpffzrzsmz713jw.png" alt="PostgreSQL source and target queries returning matching row counts" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 20: The source and RDS target each return 4 customers, 3 products, and 3 orders. The terminal also records an initial failed login followed by a successful TLS connection.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At one comparison point, both databases returned &lt;strong&gt;4 customers, 3 products, and 3 orders&lt;/strong&gt;. DMS validation subsequently reported &lt;strong&gt;3 validated tables, 0 mismatched rows, and 0 validation errors&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Those checks established something valuable about the copied data. They still left an application question open: what would happen when the target needed to create the next record?&lt;/p&gt;

&lt;h3&gt;
  
  
  The work the row counts could not reveal
&lt;/h3&gt;

&lt;p&gt;After replication, I had to repair sequence-backed IDs, defaults, and missing unique and foreign-key constraints on the target. The application user also needed the required sequence permissions.&lt;/p&gt;

&lt;p&gt;These details shape everyday application behavior: how a new row gets its ID, which values appear automatically, and which relationships the database enforces. Existing rows can look correct while that behavior still needs work.&lt;/p&gt;

&lt;p&gt;One of the permission changes in this lab was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;USAGE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;SEQUENCES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;appuser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The precise schema changes depend on the source DDL and migration configuration; the command above is &lt;strong&gt;not&lt;/strong&gt; a universal production permission recipe. In a real migration, I would migrate schema objects explicitly, compare source and target DDL, validate constraints and sequences, and test inserts and updates &lt;strong&gt;before&lt;/strong&gt; approving cutover.&lt;/p&gt;

&lt;p&gt;That changed how I read the green dashboard. It was evidence that the data checks had passed. I still needed to exercise the operations the application would depend on after cutover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 5 — The cutover happened in &lt;code&gt;.env&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Cutover introduced a boundary the earlier screenshots could not capture by themselves: the moment new writes belonged on AWS.&lt;/p&gt;

&lt;p&gt;DMS had copied and validated the data, but the application still had its original database configuration. Its &lt;code&gt;.env&lt;/code&gt; pointed to the local PostgreSQL host. Until that changed, the application would keep connecting to the source database, regardless of what the DMS dashboard showed.&lt;/p&gt;

&lt;p&gt;After validating the data, I stopped source writes, completed the migration checks, and stopped the DMS replication task. Then came the configuration change that made the migration real for the application: &lt;strong&gt;I changed the PostgreSQL host in &lt;code&gt;.env&lt;/code&gt; from the local host to the Amazon RDS endpoint.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The essential change looked like this. &lt;code&gt;DB_HOST&lt;/code&gt; is an illustrative variable name; use the name your application's database configuration actually reads.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Before: PostgreSQL on the application VM
DB_HOST=localhost

# After: Amazon RDS PostgreSQL
DB_HOST=database-migration.cojc20qugnja.us-east-1.rds.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are before-and-after alternatives, not two entries to keep in the same file. The rest of the connection configuration also needed to match the target: database name, port, application credentials, and SSL settings. A running Node.js process must reload its configuration—typically through a service restart—to use the changed &lt;code&gt;.env&lt;/code&gt; value.&lt;/p&gt;

&lt;p&gt;At this stage, the application was still on the Azure VM. Its database connection now crossed the private VPN to RDS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before: Azure app -&amp;gt; local PostgreSQL
After:  Azure app -&amp;gt; Site-to-Site VPN -&amp;gt; Amazon RDS PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That separated the database cutover from the later application move to EC2. I could test the target database with the application before changing the application's hosting environment as well.&lt;/p&gt;

&lt;p&gt;I then tested target-side writes. Additional customer records were inserted on AWS after cutover, so the later target count intentionally differed from the earlier source comparison.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frv4dc521d93c5rtgjpdb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frv4dc521d93c5rtgjpdb.png" alt="AWS DMS task showing load completed and replication stopped" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 21: The DMS task is stopped, with full load at 100%. This records the replication state; the write freeze and application cutover checks are separate steps.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The earlier count of four customers remained a useful migration checkpoint. It was no longer the expected count forever. Once AWS accepted new writes, comparing its current data with an old source snapshot required that context.&lt;/p&gt;

&lt;p&gt;The application now pointed to RDS. Its compute still lived in Azure; moving that to EC2 was the next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 6 — Moving the app to private EC2
&lt;/h2&gt;

&lt;p&gt;I copied the application to a private AWS EC2 instance over the established private network path, installed its Node.js dependencies, and recreated its connection configuration with the RDS endpoint selected during database cutover. The transfer excluded &lt;code&gt;.env&lt;/code&gt; and &lt;code&gt;node_modules&lt;/code&gt;, so dependencies and environment settings were set up on EC2 separately. The application ran as a &lt;code&gt;systemd&lt;/code&gt; service, and its &lt;code&gt;/health&lt;/code&gt; endpoint exercised a database connectivity query.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frw3quj0htdwi17r7ubzb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frw3quj0htdwi17r7ubzb.png" alt="AWS EC2 instance in a private subnet with no public IPv4 address" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 22: The AWS &lt;code&gt;private-server&lt;/code&gt; instance uses private IP &lt;code&gt;10.16.2.132&lt;/code&gt; in &lt;code&gt;priv-subnet&lt;/code&gt;, within &lt;code&gt;Demo-VPC&lt;/code&gt;, and has no public IPv4 address. This screenshot establishes its network placement, not application readiness.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The target app's connection path was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Private EC2 app (:3000) -&amp;gt; RDS security group (:5432) -&amp;gt; PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used SSL for the database connection, but the lab configuration disabled server-certificate verification (&lt;code&gt;rejectUnauthorized: false&lt;/code&gt;). &lt;strong&gt;That is not suitable for production.&lt;/strong&gt; A production migration should use RDS CA certificates and verify the server identity, with credentials in a managed secret store rather than hard-coded or committed &lt;code&gt;.env&lt;/code&gt; files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 7 — Serving the private application over HTTPS
&lt;/h2&gt;

&lt;p&gt;The final request path had several distinct hops: a browser reaching the public ALB, the ALB reaching private EC2, and the application reaching RDS.&lt;/p&gt;

&lt;p&gt;I configured an HTTP listener on &lt;strong&gt;80&lt;/strong&gt; to redirect to &lt;strong&gt;443&lt;/strong&gt;, an HTTPS listener on &lt;strong&gt;443&lt;/strong&gt; using an ACM certificate, and a target group forwarding to the app on &lt;strong&gt;HTTP 3000&lt;/strong&gt;. The health check used &lt;code&gt;/health&lt;/code&gt;. The ALB was internet-facing; the application instance remained private.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User enters https://app.example.com
              |
              v
ALB listener :443 (TLS termination)
              |
              v
Target group :3000 (health check /health)
              |
              v
Private EC2 security group allows :3000 FROM ALB security group
              |
              v
Node.js service :3000 -&amp;gt; RDS :5432
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The EC2 security group allowed TCP &lt;strong&gt;3000 from the ALB security group&lt;/strong&gt;, so the load balancer could forward requests to the application while the instance remained private.&lt;/p&gt;

&lt;p&gt;One more detail worth getting right: a wildcard ACM certificate only matches appropriate domain names. Testing an ALB-generated hostname does not prove a custom DNS name or public DNS cutover was configured. I verified the application path through the ALB, but I am not claiming a completed custom-domain DNS cutover here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq6ihgeocs3tacmk40q69.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq6ihgeocs3tacmk40q69.png" alt="Order-management application displaying customers, products, and orders" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 23: The order-management application displays six customers, three products, and three orders. Its original environment label was hard-coded; this screenshot alone does not establish the serving host, database endpoint, or HTTPS/ALB path.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The shortcuts I would revisit
&lt;/h2&gt;

&lt;p&gt;I got the lab working, but I would not carry every choice into a production setup. The single VPN tunnel and disabled database certificate verification are two clear examples. Before using this approach for a live workload, I would go back through these checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Network:&lt;/strong&gt; VPN tunnel redundancy, both-direction routes, SG/NSG policy, DNS resolution, and private-only database access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; DDL parity, indexes, constraints, sequences, roles, permissions, row counts, validation errors, application transactions, and a tested recovery path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cutover:&lt;/strong&gt; approved freeze window, final CDC lag checks, explicit source-of-truth decision, rollback criteria, and stakeholder sign-off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application:&lt;/strong&gt; process supervision, health checks, logs, connection pool settings, TLS validation, secrets management, and ALB target health.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operations:&lt;/strong&gt; monitoring, backup/restore exercises, runbooks, cost controls, and post-cutover support.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  There is still more I can do with this app
&lt;/h2&gt;

&lt;p&gt;For now, the application runs on EC2. I would like to build on that by containerizing it, storing the image in ECR, and trying ECS/Fargate or EKS. Terraform would also give me a way to recreate the infrastructure without repeating the console setup.&lt;/p&gt;

&lt;p&gt;Those would be separate exercises. This lab stops at the application and database migration; secrets management, CI/CD, and better monitoring are still on my list, along with RDS high availability and the second VPN tunnel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking back
&lt;/h2&gt;

&lt;p&gt;I started this project wanting to connect my local environment to AWS. The MTN and Airtel 5G router blockers changed the setup, but I was able to continue with Azure as the source.&lt;/p&gt;

&lt;p&gt;The part that stays with me is the database cutover. I could see the rows in RDS, but my app was still configured to use local PostgreSQL. Changing the host in &lt;code&gt;.env&lt;/code&gt; was the step that made the application use the migrated database. Moving the application itself to EC2 came afterward.&lt;/p&gt;

&lt;p&gt;These are the details I wanted to get hands-on experience with when I started. Having worked through them, I have more to check than a completed DMS task: the app's connection settings, its ability to read and write, and the path a request takes to reach it.&lt;/p&gt;

&lt;p&gt;Thanks for reading and following along with my lab. Cheers!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>azure</category>
      <category>database</category>
      <category>networking</category>
    </item>
    <item>
      <title>Migration &amp; Modernization</title>
      <dc:creator>Dimeji Ojewunmi</dc:creator>
      <pubDate>Thu, 08 Oct 2026 01:07:28 +0000</pubDate>
      <link>https://dev.to/dimeji_ojewunmi_5e27256/migration-modernization-4db3</link>
      <guid>https://dev.to/dimeji_ojewunmi_5e27256/migration-modernization-4db3</guid>
      <description>&lt;h1&gt;
  
  
  Migrated an App and PostgreSQL Database from Azure to AWS—And the “Successful” Database Migration Still Needed Fixing
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;An end-to-end migration lab: private networking, Site-to-Site VPN, AWS DMS full load + CDC, database cutover, and a private EC2 application behind an HTTPS load balancer.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Migration is easy in architecture diagrams. Draw an arrow from Azure to AWS, add a cloud icon, and everyone goes home happy. Real migrations involve routes, ports, replication state, application secrets, database defaults, and at least one moment when an ALB says &lt;strong&gt;504 Gateway Timeout&lt;/strong&gt; instead of saying hello.&lt;/p&gt;

&lt;p&gt;This was a hands-on lab, not a claim about a production enterprise cutover. I built an Azure environment to simulate an on-premises source and migrated a Node.js application and PostgreSQL database to AWS. The goal was to practice the decisions, checks, and failure modes that matter in a real migration.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Source (Azure, simulating on-prem):&lt;/strong&gt; an Ubuntu VM with a private address, a Node.js/Express order-management app on port 3000, and PostgreSQL 16 containing &lt;code&gt;customers&lt;/code&gt;, &lt;code&gt;products&lt;/code&gt;, and &lt;code&gt;orders&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target (AWS):&lt;/strong&gt; a VPC (&lt;code&gt;10.16.0.0/16&lt;/code&gt;), private Amazon RDS for PostgreSQL, AWS DMS, and a private EC2 instance running the application. An internet-facing Application Load Balancer (ALB) terminates HTTPS and forwards traffic to the private app on port 3000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Migration connectivity:&lt;/strong&gt; a route-based IPsec Site-to-Site VPN between Azure (&lt;code&gt;172.16.0.0/16&lt;/code&gt;) and AWS (&lt;code&gt;10.16.0.0/16&lt;/code&gt;). The Azure VPN connection showed &lt;strong&gt;Connected&lt;/strong&gt;; on AWS, one VPN tunnel was UP and the other DOWN. That was sufficient for the lab, but &lt;strong&gt;not&lt;/strong&gt; a highly available production VPN design.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Migration path (private traffic)
  Azure simulated on-prem                    AWS
  172.16.0.0/16                              10.16.0.0/16
  +----------------------+   IPsec VPN   +----------------------+
  | Ubuntu VM 172.16.0.4|===============&amp;gt;| DMS replication      |
  | Node.js :3000       |                | RDS PostgreSQL       |
  | PostgreSQL 16       |                | Private subnets      |
  +----------------------+                +----------------------+

                 Final application request path
  Browser --HTTPS :443--&amp;gt; Public ALB --HTTP :3000--&amp;gt; Private EC2
                                                   |
                                                   +--TCP :5432--&amp;gt; Private RDS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Security note:&lt;/strong&gt; This diagram shows the logical data flow, not every route table, gateway, or security-group rule. The public ALB is the entry point; the EC2 application and RDS database do not need public IPs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1 — Establish the source environment
&lt;/h2&gt;

&lt;p&gt;I created the Azure virtual network, private workload subnet, VPN gateway subnet, and an Ubuntu VM without a public NIC address. The source VM hosted the application and PostgreSQL database. A private VM is a good simulation of an on-prem server: reaching it requires an intentional management or network path, rather than a casual SSH connection from the internet.&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;Figure 1: The Azure VM used as the simulated on-premises source.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F48l8pnr2l43xxbtxvcat.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F48l8pnr2l43xxbtxvcat.png" alt="Azure VNet and subnet layout" width="799" height="414"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2: Azure network segmentation for the workload and VPN gateway.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The source database contained three tables: &lt;code&gt;customers&lt;/code&gt;, &lt;code&gt;products&lt;/code&gt;, and &lt;code&gt;orders&lt;/code&gt;. The application exposed a health endpoint and supported reading and writing data, giving me something meaningful to validate after migration.&lt;/p&gt;
&lt;h2&gt;
  
  
  Phase 2 — Connect Azure to AWS over IPsec
&lt;/h2&gt;

&lt;p&gt;The Azure VPN gateway and AWS virtual private gateway were configured with compatible tunnel parameters. I created an Azure Local Network Gateway representing the AWS VPN endpoint and remote address space. On the AWS side, the private route tables needed a route back to &lt;code&gt;172.16.0.0/16&lt;/code&gt; through the virtual private gateway.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbt6a3oyfj9cm3ofdkym6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbt6a3oyfj9cm3ofdkym6.png" alt="Azure VPN gateway" width="799" height="414"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 3: Azure VPN gateway used for the lab connection.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy8qdwukzmyodih20ggtl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy8qdwukzmyodih20ggtl.png" alt="Azure VPN connection status" width="799" height="414"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 4: The Azure-to-AWS VPN connection in the Connected state.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The important lesson: &lt;strong&gt;a VPN connection showing Connected is not proof that your application traffic can flow.&lt;/strong&gt; Both directions need working routes; firewalls, network security groups, and AWS security groups must permit the actual source/destination traffic. For database replication, that meant PostgreSQL TCP port &lt;strong&gt;5432&lt;/strong&gt;, not the application port &lt;strong&gt;3000&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I also treated the single operational tunnel as a lab limitation. A production design should validate redundant tunnels, failover behavior, monitoring, and appropriate routing resilience.&lt;/p&gt;
&lt;h2&gt;
  
  
  Phase 3 — Prepare AWS RDS and DMS
&lt;/h2&gt;

&lt;p&gt;I provisioned private Amazon RDS for PostgreSQL and set up AWS Database Migration Service (DMS) endpoints for the Azure source database and the AWS target. The source was PostgreSQL 16 and the RDS target reported PostgreSQL 18.3, so this exercise also required attention to engine-version differences and schema compatibility.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fynijadp7l0pgve7370n4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fynijadp7l0pgve7370n4.png" alt="DMS source endpoint" width="799" height="414"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 5: AWS DMS source endpoint for the Azure-hosted PostgreSQL database.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fevtvf4o6tdro4ebe870y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fevtvf4o6tdro4ebe870y.png" alt="DMS target endpoint" width="799" height="414"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 6: AWS DMS target endpoint for Amazon RDS PostgreSQL.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I used &lt;strong&gt;full load plus change data capture (CDC)&lt;/strong&gt;. Full load copies the existing rows; CDC then applies ongoing changes while the source is still in use. This is valuable because a migration is rarely just “copy a database and hope nobody edits anything.”&lt;/p&gt;

&lt;p&gt;The migration plan was:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Establish and test private connectivity and database access.&lt;/li&gt;
&lt;li&gt;Create and test DMS source and target endpoints.&lt;/li&gt;
&lt;li&gt;Run the pre-migration assessment and review warnings.&lt;/li&gt;
&lt;li&gt;Run full load and CDC, monitoring task and table status.&lt;/li&gt;
&lt;li&gt;Compare data and validate application read/write behavior.&lt;/li&gt;
&lt;li&gt;Freeze source writes, confirm target consistency, and stop replication.&lt;/li&gt;
&lt;li&gt;Reconfigure and move the application to AWS, then verify end-to-end access.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Phase 4 — Assess, replicate, validate
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy72xevac83vfo9zeedbu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy72xevac83vfo9zeedbu.png" alt="DMS migration task summary" width="799" height="414"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 7: The DMS task configured for the migration.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3z6zd2cg43rp1mp64s6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3z6zd2cg43rp1mp64s6.png" alt="DMS pre-migration assessment" width="799" height="414"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 8: Pre-migration assessment results.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The pre-migration assessment reported &lt;strong&gt;43 checks: 26 passed, 2 warnings, 15 skipped, and 0 failed&lt;/strong&gt;. “Skipped” and “warning” do not mean “ignore”; they tell you where manual review is still necessary.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvarhypgl4f2czhdc7woi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvarhypgl4f2czhdc7woi.png" alt="DMS validation" width="799" height="414"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 9: DMS table validation showed three validated tables, with no reported mismatches or validation errors.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3hmrbpffzrzsmz713jw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3hmrbpffzrzsmz713jw.png" alt="Source versus target database comparison" width="799" height="414"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 10: Comparing source and target row counts during migration.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At one comparison point, the source and target both showed &lt;strong&gt;4 customers, 3 products, and 3 orders&lt;/strong&gt;. These were point-in-time counts—not a substitute for comprehensive data validation. DMS validation subsequently reported &lt;strong&gt;3 validated tables, 0 mismatched rows, and 0 validation errors&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  The gotcha: rows migrated, but the schema was not application-ready
&lt;/h3&gt;

&lt;p&gt;The most useful lesson in this lab was that a healthy-looking DMS task is &lt;strong&gt;not&lt;/strong&gt; the same as a fully functional database cutover.&lt;/p&gt;

&lt;p&gt;After replication, I had to repair PostgreSQL schema behavior on the target: sequence-backed IDs, defaults, and missing unique/foreign-key constraints. I also had to grant the application user the required sequence permissions. For example, in a controlled lab where that level of access is appropriate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;USAGE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;SEQUENCES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;appuser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The precise schema changes depend on the source DDL and migration configuration; the command above is &lt;strong&gt;not&lt;/strong&gt; a universal production permission recipe. In a real migration, I would migrate schema objects explicitly, compare source and target DDL, validate constraints and sequences, and test inserts and updates &lt;strong&gt;before&lt;/strong&gt; approving cutover.&lt;/p&gt;

&lt;p&gt;Why it matters: a row-count dashboard can be green while the first real customer insert fails. Databases do not give bonus points for beautiful dashboards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 5 — Cut over the database
&lt;/h2&gt;

&lt;p&gt;Once I had validated the data, I stopped writes to the source, completed the migration checks, and stopped the DMS replication task. I then tested target-side writes. Additional customer records were inserted on the AWS side after cutover, so later target counts were intentionally different from the earlier source comparison.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frv4dc521d93c5rtgjpdb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frv4dc521d93c5rtgjpdb.png" alt="DMS task stopped after cutover" width="799" height="414"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 11: Replication stopped after the database cutover.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The distinction is important: &lt;strong&gt;a post-cutover target row count should not be compared blindly against an earlier source snapshot&lt;/strong&gt;. You need a defined validation window, a write-freeze boundary, and an understanding of which system became the source of truth.&lt;/p&gt;
&lt;h2&gt;
  
  
  Phase 6 — Move the Node.js app to private EC2
&lt;/h2&gt;

&lt;p&gt;I copied the application to a private AWS EC2 instance over the established private network path, installed its Node.js dependencies, and configured it to connect to the RDS endpoint. The transfer excluded &lt;code&gt;.env&lt;/code&gt; and &lt;code&gt;node_modules&lt;/code&gt;, allowing dependencies and configuration to be created appropriately on the target. The application ran as a &lt;code&gt;systemd&lt;/code&gt; service, and its &lt;code&gt;/health&lt;/code&gt; endpoint exercised a database connectivity query.&lt;/p&gt;

&lt;p&gt;The target app's connection path was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Private EC2 app (:3000) -&amp;gt; RDS security group (:5432) -&amp;gt; PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used SSL for the database connection, but the lab configuration disabled server-certificate verification (&lt;code&gt;rejectUnauthorized: false&lt;/code&gt;). &lt;strong&gt;That is not suitable for production.&lt;/strong&gt; A production migration should use RDS CA certificates and verify the server identity, with credentials in a managed secret store rather than hard-coded or committed &lt;code&gt;.env&lt;/code&gt; files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 7 — Put the private application behind HTTPS
&lt;/h2&gt;

&lt;p&gt;The ALB was internet-facing; the application instance remained private. I configured an HTTP listener on &lt;strong&gt;80&lt;/strong&gt; to redirect to &lt;strong&gt;443&lt;/strong&gt;, an HTTPS listener on &lt;strong&gt;443&lt;/strong&gt; using an ACM certificate, and a target group forwarding to the app on &lt;strong&gt;HTTP 3000&lt;/strong&gt;. The health check used &lt;code&gt;/health&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User enters https://app.example.com
              |
              v
ALB listener :443 (TLS termination)
              |
              v
Target group :3000 (health check /health)
              |
              v
Private EC2 security group allows :3000 FROM ALB security group
              |
              v
Node.js service :3000 -&amp;gt; RDS :5432
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;504 Gateway Timeout&lt;/strong&gt; I hit was a practical reminder that the ALB listener port and application port are separate. The listener accepted HTTPS on 443, but the target instance's security group initially did not allow the ALB to reach the application on port 3000. Once I allowed TCP 3000 &lt;strong&gt;from the ALB security group&lt;/strong&gt;, the target could respond.&lt;/p&gt;

&lt;p&gt;One more detail worth getting right: a wildcard ACM certificate only matches appropriate domain names. Testing an ALB-generated hostname does not prove a custom DNS name or public DNS cutover was configured. I verified the application path through the ALB, but I am not claiming a completed custom-domain DNS cutover here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq6ihgeocs3tacmk40q69.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq6ihgeocs3tacmk40q69.png" alt="Order management application" width="799" height="414"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 12: The order-management application used for end-to-end testing. The page's original environment label was hard-coded and is not, by itself, proof of which server handled the request.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would check before calling a real production migration complete
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Network:&lt;/strong&gt; VPN tunnel redundancy, both-direction routes, SG/NSG policy, DNS resolution, and private-only database access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; DDL parity, indexes, constraints, sequences, roles, permissions, row counts, validation errors, application transactions, and a tested recovery path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cutover:&lt;/strong&gt; approved freeze window, final CDC lag checks, explicit source-of-truth decision, rollback criteria, and stakeholder sign-off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application:&lt;/strong&gt; process supervision, health checks, logs, connection pool settings, TLS validation, secrets management, and ALB target health.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operations:&lt;/strong&gt; monitoring, backup/restore exercises, runbooks, cost controls, and post-cutover support.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Future modernization (not implemented in this lab)
&lt;/h2&gt;

&lt;p&gt;After a successful rehost, the next stage could be containerizing the app, pushing images to Amazon ECR, deploying to ECS/Fargate or EKS, managing infrastructure with Terraform, using AWS Secrets Manager, adding CI/CD, and strengthening observability. I'd also revisit RDS high availability and VPN redundancy.&lt;/p&gt;

&lt;p&gt;Those are &lt;strong&gt;next steps&lt;/strong&gt;, not things I am claiming to have delivered in this migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The biggest takeaway wasn't a service name. It was learning that &lt;strong&gt;connectivity, replication, schema fidelity, application behavior, and cutover are five different things&lt;/strong&gt;—and every one must be validated independently.&lt;/p&gt;

&lt;p&gt;A green DMS status is good news. A working application that can read &lt;em&gt;and write&lt;/em&gt; safely after cutover is better news.&lt;/p&gt;

&lt;p&gt;It's been a long read, thank you for staying through to the end... Cheers!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>database</category>
      <category>postgres</category>
    </item>
    <item>
      <title>DORA METRICS EXPORTER</title>
      <dc:creator>Dimeji Ojewunmi</dc:creator>
      <pubDate>Thu, 08 Aug 2024 20:42:15 +0000</pubDate>
      <link>https://dev.to/dimeji_ojewunmi_5e27256/dora-metrics-exporter-47b4</link>
      <guid>https://dev.to/dimeji_ojewunmi_5e27256/dora-metrics-exporter-47b4</guid>
      <description>&lt;h1&gt;
  
  
  Introduction to Dora Metrics
&lt;/h1&gt;

&lt;p&gt;DORA Metrics are key performance indicators used to measure the effectiveness of a software delivery process. They help teams understand the efficiency of their DevOps practices by tracking key aspects of the CI/CD pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Below are the outlined reason stipulate why we need to leverage the need of Dora Metrics in an organization
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Deployment Frequency: Measures how often deployments occur. High frequency indicates a fast, iterative development process.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lead Time for Changes: The time it takes from committing code to deploying it in production. Short lead times suggest an efficient pipeline.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Change Failure Rate: The percentage of deployments that result in a failure in production. Lower rates indicate more stable and reliable releases.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Time to Restore Service: The time it takes to recover from a failure in production. Faster recovery times imply a more resilient system.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Installation Setup
&lt;/h1&gt;

&lt;p&gt;The following git command below are directories and files to be opened in the cause of our Dora metric exporter installation&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;dora_metrics_exporter
&lt;span class="nb"&gt;cd &lt;/span&gt;dora_metrics_exporter
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
&lt;span class="nb"&gt;touch &lt;/span&gt;requirements.txt .env exporter.py
&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate  &lt;span class="c"&gt;# On Windows use `venv\Scripts\activate`&lt;/span&gt;
&lt;span class="nb"&gt;touch &lt;/span&gt;requirements.txt .env exporter.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;requirements.txt&lt;/code&gt;context below
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flask
prometheus_client
requests
python-dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run the script below right after the opening of 'requirements.txt'
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;.env&lt;/code&gt; context below
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GITHUB_TOKEN=your_github_token
REPO_OWNER=holadmex (GitHub Username)
REPO_NAME=hng_boilerplate_expressjs (Project Repository Name)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;exporter.py&lt;/code&gt; context below
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# exporter.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;prometheus_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;start_http_server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Gauge&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="c1"&gt;# Load environment variables from .env file
&lt;/span&gt;&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Configure logging
&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Metrics
&lt;/span&gt;&lt;span class="n"&gt;DEPLOYMENT_FREQUENCY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Gauge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;deployment_frequency&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Frequency of deployments&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;LEAD_TIME_FOR_CHANGES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Gauge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lead_time_for_changes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Lead time for changes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;CHANGE_FAILURE_RATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Gauge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;change_failure_rate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Rate of change failures&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;TIME_TO_RESTORE_SERVICE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Gauge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time_to_restore_service&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Time to restore service&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# GitHub Configuration
&lt;/span&gt;&lt;span class="n"&gt;GITHUB_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;REPO_OWNER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;REPO_OWNER&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;REPO_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;REPO_NAME&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;HEADERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;token &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Function to fetch data from GitHub API
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_github_data&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fetching GitHub data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://api.github.com/repos/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;REPO_OWNER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;REPO_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/stats/code_frequency&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Data fetched successfully&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_metrics&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Calculating metrics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch_github_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Perform calculations for DORA metrics here
&lt;/span&gt;
        &lt;span class="c1"&gt;# Dummy values for demonstration
&lt;/span&gt;        &lt;span class="n"&gt;DEPLOYMENT_FREQUENCY&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;LEAD_TIME_FOR_CHANGES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;CHANGE_FAILURE_RATE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;TIME_TO_RESTORE_SERVICE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Metrics calculated and set&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error calculating metrics: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Start Prometheus metrics server
&lt;/span&gt;    &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Starting Prometheus metrics server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;start_http_server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8001&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Prometheus metrics server started on port 8001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Schedule metric calculations
&lt;/span&gt;    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;calculate_metrics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Fetch data every 5 minutes
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The code below execute the Dora Metric Exporter installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python exporter.py
OR
&lt;span class="nb"&gt;nohup &lt;/span&gt;python exporter.py &amp;amp; &lt;span class="o"&gt;(&lt;/span&gt;This cmd runs your application &lt;span class="k"&gt;in &lt;/span&gt;the background without your terminal being held hostage&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  NB:
&lt;/h2&gt;

&lt;p&gt;On completion of the installation, Dora metrics job needs to be configured or added to Prometheus config file in order to export the generated metrics from Dora exporter to Prometheus which will be visualized on Grafana dashboard through Grafana data source.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scrape_configs:
  - job_name: &lt;span class="s1"&gt;'dora_metrics'&lt;/span&gt;
    static_configs:
      - targets: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'localhost:8001'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  DORA EXPORTER METRICS QUERY TO BE VISUALISED ON GRAFANA DASHBOARD
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deployment_frequency
lead_time_for_changes
change_failure_rate
time_to_restore_service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thank you for reading to the end,&lt;br&gt;
Happy Hands On Practice!.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Python Messaging Queue App Development</title>
      <dc:creator>Dimeji Ojewunmi</dc:creator>
      <pubDate>Tue, 16 Jul 2024 09:46:01 +0000</pubDate>
      <link>https://dev.to/dimeji_ojewunmi_5e27256/python-messaging-queue-app-development-2a25</link>
      <guid>https://dev.to/dimeji_ojewunmi_5e27256/python-messaging-queue-app-development-2a25</guid>
      <description>&lt;p&gt;&lt;strong&gt;Hello&lt;/strong&gt; guys,&lt;/p&gt;

&lt;p&gt;Today we'll be developing a &lt;strong&gt;Python Messaging Queue Test&lt;/strong&gt;, which consist of the following 'Tools' as our requirement.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;RabbitMQ&lt;/li&gt;
&lt;li&gt;Celery&lt;/li&gt;
&lt;li&gt;Ngrok&lt;/li&gt;
&lt;li&gt;Nginx&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Lets talk about the theoretical aspect of the core tools we'll be using in the course of the task, courtesy of &lt;strong&gt;(HNG Internship)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What is &lt;strong&gt;RabbitMQ&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;RabbitMQ is an open-source messaging software that implements the Advanced Message Queuing Protocol (AMQP) and is widely used for managing messaging queues. RabbitMQ here also serves as Amazon Simple Queue Service(SQ) distributed messaging system which is fully managed by AWS, makes it easy to decouple and scale microservices, distributed systems, and serverless applications.&lt;/p&gt;

&lt;p&gt;What is &lt;strong&gt;Celery&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Celery is an asynchronous task queue/job queue based on distributed message passing. its main purpose is to handle "asynchronous" task queue/job queue that handles the execution of background tasks.&lt;/p&gt;

&lt;p&gt;Asynchronous programming is a form of programming that allows a unit of work (like a function or a task) to start processing before the previous unit of work finishes which also means its serves task to function independently.&lt;/p&gt;

&lt;p&gt;What is &lt;strong&gt;Ngrok&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Ngrok is a tool that creates secure tunnels from a public endpoint (like the internet) to a locally running web service or application. It allows developers to expose their local development stage environment to the internet securely, making it easier to test and share work-in-progress applications with team members without deploying them to a live environment. &lt;/p&gt;

&lt;p&gt;What is &lt;strong&gt;Nginx&lt;/strong&gt;? &lt;/p&gt;

&lt;p&gt;Nginx is a web server and reverse proxy server that is widely used for serving web content, handling load balancing, and acting as a proxy for email services. It's known for its speed, reliability, and low resource usage mainly in development to production stage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lets hop into the business of the day, getting hands on the project task as we progress in the course of the blog post.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Installation Prerequisite&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sudo apt-get update&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo apt-get install rabbitmq-server&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo apt install nginx -y&lt;/code&gt;&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://ngrok.com/docs/guides/device-gateway/linux/" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fngrok.com%2Fdocs%2Fimg%2Fngrok-docs-opengraph.png" height="auto" class="m-0"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://ngrok.com/docs/guides/device-gateway/linux/" rel="noopener noreferrer" class="c-link"&gt;
          Linux | ngrok documentation
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          Learn how to install ngrok on a remote Linux device to provide secure access and management.
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fngrok.com%2Fdocs%2Fimg%2Ffavicon.ico"&gt;
        ngrok.com
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
 (Redirection link to installl Ngrok)
&lt;h2&gt;
  
  
  On successful completion of installing Ngrok, log on to &lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://ngrok.com/" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.prod.website-files.com%2F63ed4bc7a4b189da942a6b8c%2F66857e35669ffc5b744f4ded_feature-homepage-new-1-min.png" height="auto" class="m-0"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://ngrok.com/" rel="noopener noreferrer" class="c-link"&gt;
          ngrok | API Gateway, IoT Device Gateway, Secure Tunnels for Containers, Apps &amp;amp; APIs
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          ngrok is a secure ingress platform that enables developers to add global server load balancing, reverse proxy, firewall, API gateway and Kubernetes Ingress to applications and APIs.
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.prod.website-files.com%2F63ed4bc7a4b189da942a6b8c%2F63ef8624e010d9861920be4e_ngrok-favicon.svg"&gt;
        ngrok.com
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
 and sign up, to have a generate token which will be integrated with your project directory on your Linux terminal environment. Run the command below
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ngrok authtoken &amp;lt;your generate token&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hold still, we'll be writing few couple blocks of code using Python and installing the required Python Framework/Library to execute our Python blocks of code functions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First we'll open a directory on our Linux Vm (e.g Ubuntu)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mkdir messaging-queue-app&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cd messaging-queue-app&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;touch app.py .env ngnix.conf&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Created &lt;strong&gt;app.py&lt;/strong&gt; File context&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Celery&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;smtplib&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;email.mime.text&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MIMEText&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;email.mime.multipart&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MIMEMultipart&lt;/span&gt;

&lt;span class="c1"&gt;# Load environment variables
&lt;/span&gt;&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize Flask application
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Configure Celery
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CELERY_BROKER_URL&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pyamqp://guest@localhost//&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CELERY_RESULT_BACKEND&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rpc://&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Celery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;broker&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CELERY_BROKER_URL&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;celery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Configure logging
&lt;/span&gt;&lt;span class="n"&gt;log_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expanduser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;~&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;messaging_system.log&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;log_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filemode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%(asctime)s - %(message)s&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Celery task to send email
&lt;/span&gt;&lt;span class="nd"&gt;@celery.task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_email&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;from_email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;EMAIL_USER&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;from_password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;EMAIL_PASSWORD&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;smtp_server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;smtp.mail.yahoo.com&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;smtp_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;465&lt;/span&gt;
    &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Official Email from Flask Application&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;This is a test email sent from the Flask application.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MIMEMultipart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;From&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;from_email&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;To&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;to_email&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Subject&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MIMEText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;plain&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Connecting to SMTP server &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;smtp_server&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;smtp_port&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;smtplib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SMTP_SSL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;smtp_server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;smtp_port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Logging in to SMTP server&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from_password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Sending email&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - Email sent to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;to_email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Failed to send email: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Flask routes
&lt;/span&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sendmail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;send_email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sendmail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - Email queued for: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sendmail&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Email queued.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;talktome&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - Logged the current time.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Logged the current time.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Welcome to the messaging system. Use /?sendmail=email or /?talktome.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/logs&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;log_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;filtered_logs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;log_content&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Email sent to&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Email queued for&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filtered_logs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;mimetype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text/plain&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Log file not found.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;

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

&lt;/div&gt;



&lt;h1&gt;
  
  
  NB:
&lt;/h1&gt;

&lt;p&gt;The Python Messaging queue application development here was setup to make use of (Yahoomail SMTP Send). You you're to put in your email address, and also generate an app token in your Yahoomail account security path, and make the configuration changes in the (.env)&lt;/p&gt;

&lt;p&gt;Created .env File context&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EMAIL_USER="ojewumi_dimeji@ymail.com"
EMAIL_PASSWORD="clndcnbjfpaelrj1"
SMTP_SERVER="smtp.mail.yahoo.com"
SMTP_PORT=465
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Created nginx.conf File context&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
    listen 80;
    server_name localhost;
    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On filling each files with their respective blocks of codes, there are few more Python requirement Frameworks/libraries to be installed right in our project directory using the (virtual Linux environment cmd)&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sudo apt-get install python3-venv&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Right after the &lt;code&gt;python3-venv&lt;/code&gt; installation, execute the following command below on your Linux terminal. This command create a separate virtual environment in your project directory for your block of code function to carry out its functions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python3 -m venv messaging-queue
source messaging-queue/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On carry out the above, we'll commence installation of our Python Frameworks/Libraries right inside our created (&lt;code&gt;venv messaging-queue&lt;/code&gt;) which you  will be redirected to on executing the (&lt;code&gt;source messaging-queue/bin/activate&lt;/code&gt;) cmd&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install celery
pip install flask
pip install python-dotenv
pip freeze &amp;gt; requirements.txt (This CMD output all the installed Python pip installation for the application to run successfully)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Freight not, lol, we're  95% done to the completion of our project on carrying out the following command to execute our Python Messaging Queue Test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First step:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo rabbitmq-server&lt;/code&gt; (This CMD launch our &lt;strong&gt;RabbitMQ&lt;/strong&gt; sever to run for the course of our python messaging queue app test)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo rabbitmq-server -detached&lt;/code&gt; (This helps to run your RabbitMQ sever in the background without the terminal being held hostage)&lt;/p&gt;

&lt;h2&gt;
  
  
  Open another sperate terminal on executing each rabbitmq-server and celery cmd
&lt;/h2&gt;

&lt;p&gt;"Optional, but a must on first attempt with the cmd above and below"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second step:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;celery -A app.celery worker --loglevel=info&lt;/code&gt; (This opens your &lt;strong&gt;Celery&lt;/strong&gt; monitoring environment on checking for errors and successful &lt;code&gt;?sendmail&lt;/code&gt; execution of your python messaging queue app)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;celery -A app.celery worker --loglevel=info --detach&lt;/code&gt; (This helps to run your &lt;strong&gt;Celery&lt;/strong&gt; in the background without the terminal being held hostage.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third step:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;python3 app.py&lt;/code&gt; (This CMD lunch your web app development URL to be accessed on your localhost web browser.)&lt;/p&gt;

&lt;p&gt;nohup python3 app.py &amp;gt; app.log 2&amp;gt;&amp;amp;1 &amp;amp; (This helps to lunch the app in the background without the terminal being held hostage, and also output the return function to app.log.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Here are the following path to the application web redirection on sending your queued email locally without Ngrok
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;localhost:5000
localhost:5000/?sendmail=happy-learning@gmail.com (Email you which to send message to)
localhost:+5000/?talktome
localhost:5000/logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Finally, on exposing our &lt;code&gt;Local Development Test&lt;/code&gt; to other team members, carry out the installation below;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sudo apt-get install screen&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  On completion of the installation, run;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;screen -S ngrok&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ngrok http 5000&lt;/code&gt; (You should see an outputted endpoint "&lt;a href="https://e7c9-44-202-0-101.ngrok-free.app/" rel="noopener noreferrer"&gt;https://e7c9-44-202-0-101.ngrok-free.app/&lt;/a&gt;" which serves as your URL to be publicly accessed.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Here are the following path to the application web redirection on sending your queued email with Ngrok
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://e7c9-44-202-0-101.ngrok-free.app/
https://e7c9-44-202-0-101.ngrok-free.app:5000/?sendmail=happy-learning@gmail.com
https://e7c9-44-202-0-101.ngrok-free.app:5000/?talktome
https://e7c9-44-202-0-101.ngrok-free.app:5000/logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've come to the End of the Task,&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Celebrate yourself&lt;/strong&gt; for not holding back on the success of completing the &lt;strong&gt;Python Messaging Queue App Development&lt;/strong&gt;,&lt;/p&gt;

&lt;p&gt;Happy reading,&lt;br&gt;
Thank you.&lt;br&gt;
Dimeji...&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Linux Bash Script User Creation</title>
      <dc:creator>Dimeji Ojewunmi</dc:creator>
      <pubDate>Wed, 03 Jul 2024 19:06:07 +0000</pubDate>
      <link>https://dev.to/dimeji_ojewunmi_5e27256/linux-bash-script-user-creation-ml8</link>
      <guid>https://dev.to/dimeji_ojewunmi_5e27256/linux-bash-script-user-creation-ml8</guid>
      <description>&lt;p&gt;Hi Enthusiast DevOps Engineer,&lt;/p&gt;

&lt;p&gt;Today we'll be having a deep insight on the need to write an automated &lt;strong&gt;users bash script&lt;/strong&gt; in an organization, and also having all the metrics and password auto generated stored in a LOG_FILE.&lt;/p&gt;

&lt;p&gt;The main reason for the creation of the &lt;strong&gt;users bash scrip&lt;/strong&gt; is to mitigate repetitive user/group creation task manually, which might be daunting in most cases, and also to have an autonomous control of employees who have assess to the organization Linux workflow environment.&lt;/p&gt;

&lt;p&gt;Before i proceed, the user bash script creation is in curtesy of (&lt;strong&gt;HNG Internship&lt;/strong&gt;) program, which has just kicked off for the year 2024 "11th cohort", helping various beginners and intermediate tech enthusiast in having a real world project experience. To learn more about HNG internship program, kindly visit &lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://hng.tech/internship" rel="noopener noreferrer"&gt;
      hng.tech
    &lt;/a&gt;
&lt;/div&gt;
, &lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://hng.tech/premium" rel="noopener noreferrer"&gt;
      hng.tech
    &lt;/a&gt;
&lt;/div&gt;
.

&lt;p&gt;Now lets delve to the business of the day, seat back, make use of your seat belt, while we journey through this together.&lt;/p&gt;

&lt;p&gt;Firstly, we'll create a text file called &lt;code&gt;users&lt;/code&gt; filled with the content below, which is dependent on our bash script file to execute its functions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;light; sudo,dev,www-data
 idimma; sudo
 mayowa; dev,www-data
 dmex; devops,sysops
 iosegbo; sysops
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Secondly we'll be creating a file called &lt;code&gt;create_users.sh&lt;/code&gt; which will run as our bash script file with the code below&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;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# Autogenerate Password and Metrics will be sent here&lt;/span&gt;
&lt;span class="nv"&gt;LOG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/log/user_management.log"&lt;/span&gt;
&lt;span class="nv"&gt;PASSWORD_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/secure/user_passwords.txt"&lt;/span&gt;


&lt;span class="c"&gt;# This will create a /var/secure file, and also assign a &amp;lt;write, read &amp;amp; execution&amp;gt; permission to the created file.&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /var/secure
&lt;span class="nb"&gt;chmod &lt;/span&gt;700 /var/secure

&lt;span class="c"&gt;# Create or clear the log and password files&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$PASSWORD_FILE&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 &lt;span class="nv"&gt;$PASSWORD_FILE&lt;/span&gt;

&lt;span class="c"&gt;# Function to generate random password&lt;/span&gt;
generate_password&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="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-base64&lt;/span&gt; 12&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="c"&gt;# Read the input file, which is the &amp;lt;users&amp;gt; file we created to be dependent on our bash script code function execution&lt;/span&gt;
&lt;span class="nv"&gt;INPUT_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="c"&gt;# Process each line in the file&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; line&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c"&gt;# Ignore characters before the semicolon&lt;/span&gt;
  &lt;span class="nv"&gt;after_semicolon&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;line&lt;/span&gt;&lt;span class="p"&gt;#*;&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="c"&gt;# Split the line into items separated by commas&lt;/span&gt;
  &lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-ra&lt;/span&gt; items &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$after_semicolon&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="c"&gt;# Ensure a group exists for each item&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;item &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nv"&gt;item&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;$item&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | xargs&lt;span class="si"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;# Trim whitespace&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;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$item&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
      if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; getent group &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Creating group: &lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nb"&gt;sudo &lt;/span&gt;groupadd &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Group already exists: &lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="k"&gt;fi
    fi
  done
done&lt;/span&gt; &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;


&lt;span class="c"&gt;# Process each line in the input file&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;';'&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; user &lt;span class="nb"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c"&gt;# Trim whitespace&lt;/span&gt;
    &lt;span class="nv"&gt;user&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;$user&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | xargs&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;groups&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;$groups&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | xargs&lt;span class="si"&gt;)&lt;/span&gt;


    &lt;span class="c"&gt;# Create user with home directory and primary group&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &amp;amp;&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;useradd &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"User &lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="s2"&gt; was created successfully."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"User &lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="s2"&gt; already exists."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;
    &lt;span class="k"&gt;fi&lt;/span&gt;

    &lt;span class="c"&gt;# Set user's groups&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;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$groups&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;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; &lt;span class="nv"&gt;$groups&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"User &lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="s2"&gt; added to group: &lt;/span&gt;&lt;span class="nv"&gt;$groups&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;
    &lt;span class="k"&gt;fi&lt;/span&gt;

    &lt;span class="c"&gt;# Auto Generate and set password&lt;/span&gt;
    &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;generate_password&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;$user&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$password&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | chpasswd
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$password&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$PASSWORD_FILE&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Password for user &lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="s2"&gt; set."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;


&lt;span class="k"&gt;done&lt;/span&gt; &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Thirdly, we'll have to assign an execution permission to the created bash script file &lt;code&gt;chmod 700 create_users.sh&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Lastly, the command to execute our bash script file is&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo ./create_users.sh&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;The command above will execute the above bash script function which is written as a code, enabling all the users to be assigned to the specified group and also enabling each users to have an assigned encrypted password which will be redirected or logged to a &lt;code&gt;LOG_FILE&lt;/code&gt; encoded in the script.&lt;/p&gt;

&lt;p&gt;Here are the list of commands to execute on our Linux VM to check the following output of our bash script block of code&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;sudo cat /var/log/user_management.log&lt;/strong&gt; (This cmd checks for the activities which has taken place in the course of the bash script execution i.e "MERTICS").&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;sudo cat /var/secure/user_passwords.txt&lt;/strong&gt; (This cmd checks for the auto generated password assigned to each users).&lt;/p&gt;

&lt;p&gt;To confirm validity of your user creation, simply execute the following command below&lt;/p&gt;

&lt;p&gt;&lt;code&gt;su dmex&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then copy the encrypted password generated for &lt;code&gt;dmex&lt;/code&gt; under &lt;strong&gt;sudo cat /var/secure/user_passwords.txt&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thank you for taking your time in reading through to the completion of the blog post, do have a wonderful time.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
