<?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: Muhammad Arief Rahman</title>
    <description>The latest articles on DEV Community by Muhammad Arief Rahman (@insomnius).</description>
    <link>https://dev.to/insomnius</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2506384%2Fdab71ddc-1019-4c25-b383-388a523413cc.jpeg</url>
      <title>DEV Community: Muhammad Arief Rahman</title>
      <link>https://dev.to/insomnius</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/insomnius"/>
    <language>en</language>
    <item>
      <title>Making TailwindCSS IntelliSense Work with Phlex in VSCode</title>
      <dc:creator>Muhammad Arief Rahman</dc:creator>
      <pubDate>Mon, 28 Apr 2025 15:31:47 +0000</pubDate>
      <link>https://dev.to/insomnius/making-tailwindcss-intellisense-work-with-phlex-in-vscode-3o7j</link>
      <guid>https://dev.to/insomnius/making-tailwindcss-intellisense-work-with-phlex-in-vscode-3o7j</guid>
      <description>&lt;p&gt;When working with Phlex—Ruby's object-oriented view component library—you might notice that TailwindCSS IntelliSense doesn't work out of the box. This happens because Phlex components are defined in Ruby files, which TailwindCSS doesn't recognize by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;In Phlex, you write HTML using Ruby syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"flex items-center justify-between"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# content here&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But VSCode's TailwindCSS extension won't provide autocomplete or linting for these classes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;Add these custom settings to your VSCode configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tailwindCSS.includeLanguages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ruby"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"html"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"[ruby]"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tailwindCSS.experimental.classRegex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;bclass:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;s*[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;']([^&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;']*)[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;']"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"[erb]"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tailwindCSS.experimental.classRegex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;bclass:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;s*[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;']([^&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;']*)[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;']"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration does two important things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tells the Tailwind extension to treat Ruby files as HTML&lt;/li&gt;
&lt;li&gt;Provides a regex pattern to identify Tailwind classes in both Ruby and ERB files&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Happy coding with Phlex and Tailwind!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>phlex</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>How Unicorn Scale Company Detect Millions of Violated Ecommerce Product Everyday</title>
      <dc:creator>Muhammad Arief Rahman</dc:creator>
      <pubDate>Wed, 02 Apr 2025 06:39:48 +0000</pubDate>
      <link>https://dev.to/insomnius/how-unicorn-scale-company-detect-millions-of-violated-ecommerce-product-everyday-4bn4</link>
      <guid>https://dev.to/insomnius/how-unicorn-scale-company-detect-millions-of-violated-ecommerce-product-everyday-4bn4</guid>
      <description>&lt;p&gt;Cover Photo by &lt;a href="https://unsplash.com/ja/@picsbyjameslee?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;James Lee&lt;/a&gt; on &lt;a href="https://unsplash.com/ja/%E5%86%99%E7%9C%9F/%E3%83%A6%E3%83%8B%E3%82%B3%E3%83%BC%E3%83%B3%E3%82%A4%E3%83%B3%E3%83%95%E3%83%AC%E3%83%BC%E3%82%BF%E3%83%96%E3%83%AB%E7%8E%A9%E5%85%B7-qSf_4bNsoWc?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At Unicorn Scale Company, ensuring compliance with marketplace policies is a critical challenge. Sellers often attempt to upload prohibited items like drugs, animals, and sex toys by slightly modifying images to evade detection. Traditional hash-based matching (MD5, SHA) fails because even minor changes in an image result in completely different hashes. To solve this, we implemented perceptual hashing (pHash), a technique that identifies visually similar images even when altered. This article details our approach, implementation, and validation process.&lt;/p&gt;

&lt;p&gt;Unicorn Scale Company processes around 3 to 5 million new product listings per day, making it essential to have an efficient and scalable solution for detecting policy violations.&lt;/p&gt;

&lt;p&gt;But this regulations is usually different from each countries, in my country (Indonesia) for example, the government forbid us to sell sex toys, illegal drugs and others, some of the product often got passed from the evaluations, for example that we can see 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.amazonaws.com%2Fuploads%2Farticles%2Flxx46e53dm4lvhoi5vo4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flxx46e53dm4lvhoi5vo4.png" alt="An example of violated product shown in marketplaces" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn15wpr1d6jyieaxg4wxi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn15wpr1d6jyieaxg4wxi.png" alt="An example of violated product shown in marketplaces" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both images depict products containing illegal drugs and sex toys, which are prohibited from being sold.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Perceptual Hashing?
&lt;/h2&gt;

&lt;p&gt;Unlike cryptographic hashes, which change drastically with small modifications, perceptual hashing produces similar hashes for visually similar images. This allows us to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detect duplicate or near-duplicate images.&lt;/li&gt;
&lt;li&gt;Identify sellers re-uploading prohibited items with slight modifications.&lt;/li&gt;
&lt;li&gt;Improve enforcement of policy violations without relying on metadata or text descriptions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detect bad actor sellers who often upload similar product images to evade detection&lt;/strong&gt;. This was a recurring challenge on our platform, where policy-violating products were frequently re-listed with minor alterations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  System Architecture
&lt;/h2&gt;

&lt;p&gt;Below is a simplified architecture diagram illustrating how our product violation detection system works and integrates with the product service as its backbone.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Workflow Overview:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ops Team Workflow&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A1. &lt;strong&gt;Operations teams establish new P-hash rules based on observations&lt;/strong&gt;: Our dedicated team stays updated on the latest tactics used by bad actors.&lt;/p&gt;

&lt;p&gt;A2. &lt;strong&gt;Requests are forwarded to the policy service by GCLB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A3. &lt;strong&gt;P-hash list is updated with specific actions&lt;/strong&gt;: The operations team can either upload an image or select a product from our admin dashboard to remove policy-violating products and register the corresponding P-hash image in the policy service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Product Detection Workflow&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;B1. &lt;strong&gt;Seller Creates or Updates a Product&lt;/strong&gt;: We process up to 5 million new product listings daily, and updates can reach 10 to 15 million products per day.&lt;/p&gt;

&lt;p&gt;B2. &lt;strong&gt;Requests are forwarded to the product service by GCLB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;B3. &lt;strong&gt;Product data is saved to the product database&lt;/strong&gt;: Any changes to a product are stored in the product database.&lt;/p&gt;

&lt;p&gt;B4. &lt;strong&gt;Product service publishes an event&lt;/strong&gt;: Product updates trigger events in our pub-sub topic, which other microservices listen to.&lt;/p&gt;

&lt;p&gt;B5. &lt;strong&gt;Policy evaluation&lt;/strong&gt;: The policy service consumer retrieves messages from the pub-sub system.&lt;/p&gt;

&lt;p&gt;B6. &lt;strong&gt;P-hash conversion&lt;/strong&gt;: All product images are converted into P-hash format by the policy service.&lt;/p&gt;

&lt;p&gt;B7. &lt;strong&gt;Search for similar P-hashes&lt;/strong&gt;: We search for similar P-hashes in our database using XOR and Hamming distance techniques. For example, we execute the following query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT image_id, 
       bit_count(perceptual_hash # &amp;lt;actual_query_hash_value&amp;gt;) AS hamming_distance
FROM image_hashes
ORDER BY hamming_distance ASC
LIMIT 10;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;B8. &lt;strong&gt;Take action based on P-hash rule&lt;/strong&gt;: If a product’s P-hash matches the threshold for Hamming distance, we take specific actions based on the saved rules.&lt;/p&gt;

&lt;p&gt;This architecture guarantees fast and scalable image moderation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Implementation in Golang
&lt;/h2&gt;

&lt;p&gt;Now that we've covered the architecture, let's explore a practical implementation of perceptual hashing in Golang. We'll walk through the step-by-step process of converting an image into a pHash and comparing it with others, demonstrating how perceptual hashing functions in real-world applications.  &lt;/p&gt;

&lt;p&gt;Since the original product might be inappropriate for &lt;strong&gt;dev.to&lt;/strong&gt;, I'll use a picture of a cute cat instead. 🐈🐈😺  &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%2Fgithub.com%2Finsomnius%2Ftools%2Fblob%2Fmaster%2Fexamples%2Fperceptualhash-ecommerce%2Fsample%2Fcat.png%3Fraw%3Dtrue" 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%2Fgithub.com%2Finsomnius%2Ftools%2Fblob%2Fmaster%2Fexamples%2Fperceptualhash-ecommerce%2Fsample%2Fcat.png%3Fraw%3Dtrue" alt="A picture of a cat" width="400" height="265"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;I'll provide a high-level overview of how it works without delving too deeply into the low-level mechanics of pHash. For this demonstration, I'll be using a custom tool that I built. You can check out the &lt;a href="https://github.com/insomnius/tools" rel="noopener noreferrer"&gt;package repository here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Converting an Image into pHash
&lt;/h3&gt;

&lt;p&gt;To generate a perceptual hash from an image, we first configure the hashing process with debugging enabled. This allows us to store intermediate visualizations for better analysis. Below is the updated implementation in Golang using the &lt;strong&gt;perceptualhash&lt;/strong&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"path/filepath"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/insomnius/tools/perceptualhash"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Enable debugging and set paths for intermediate images&lt;/span&gt;
    &lt;span class="n"&gt;conf&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;perceptualhash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&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="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DebugParameter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PreprocessedImagePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./debug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"preprocessed_cat.png"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DebugParameter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VisualizedImagePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./debug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"visualized_cat.png"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Ensure the debug directory exists&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./debug"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsNotExist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./debug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0755&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Generate pHash for the given image&lt;/span&gt;
    &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;perceptualhash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cat.png"&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Print the computed hash&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Explanation:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Configuration:&lt;/strong&gt; Debugging is enabled to save intermediate images for inspection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Directory Handling:&lt;/strong&gt; The script ensures the &lt;code&gt;./debug&lt;/code&gt; directory exists before writing debug images.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hash Generation:&lt;/strong&gt; The &lt;code&gt;FromPath&lt;/code&gt; function processes the image (&lt;code&gt;cat.png&lt;/code&gt;) and generates a perceptual hash.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Handling:&lt;/strong&gt; If an issue occurs (e.g., file not found), the program logs an error and exits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output:&lt;/strong&gt; The computed perceptual hash is printed to the console.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are the output results, including the generated hash and debug images:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hash Output:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0hfnesytw6ppd5c1mik.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0hfnesytw6ppd5c1mik.png" alt="Hash output" width="167" height="44"&gt;&lt;/a&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Preprocessed Image (Scaled &amp;amp; Grayscale):&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffr03meqg1xu9n6gyqcwj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffr03meqg1xu9n6gyqcwj.png" alt="Scaled image" width="32" height="32"&gt;&lt;/a&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Visualized pHash Representation:&lt;/strong&gt; (&lt;em&gt;the image is small so you need to zoom in&lt;/em&gt;)   &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7xdxjxfoa9so967cpq0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7xdxjxfoa9so967cpq0.png" alt="Visualized pHash image" width="8" height="8"&gt;&lt;/a&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These debug outputs help visualize how the image is transformed during the perceptual hashing process. 🚀&lt;/p&gt;

&lt;p&gt;Next, let’s compare the original image with its altered versions—one that has been blurred, another that has been corrupted, and a third that has been resized.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blurred Cat Image:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Finsomnius%2Ftools%2Fblob%2Fmaster%2Fexamples%2Fperceptualhash-ecommerce%2Fsample%2Fcat_blurred.png%3Fraw%3Dtrue" 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%2Fgithub.com%2Finsomnius%2Ftools%2Fblob%2Fmaster%2Fexamples%2Fperceptualhash-ecommerce%2Fsample%2Fcat_blurred.png%3Fraw%3Dtrue" alt="Blurred cat image" width="600" height="398"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corrupted Cat Image:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Finsomnius%2Ftools%2Fblob%2Fmaster%2Fexamples%2Fperceptualhash-ecommerce%2Fsample%2Fcat_broken.jpg%3Fraw%3Dtrue" 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%2Fgithub.com%2Finsomnius%2Ftools%2Fblob%2Fmaster%2Fexamples%2Fperceptualhash-ecommerce%2Fsample%2Fcat_broken.jpg%3Fraw%3Dtrue" alt="Broken cat image" width="760" height="504"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resized Cat Image:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30caxh5k35zfau1sszlk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30caxh5k35zfau1sszlk.png" alt="Resized cat image" width="67" height="44"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;Despite these modifications, the generated perceptual hashes remain highly similar, with a Hamming distance score of just &lt;strong&gt;1&lt;/strong&gt;, demonstrating strong resilience to image alterations.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Comparison Output:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2256j6svvdkz7i4suq5r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2256j6svvdkz7i4suq5r.png" alt="Script output for the three images" width="563" height="335"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;This highlights how perceptual hashing offers a cost-effective alternative to complex computer vision models. It enables robust similarity detection with minimal computational overhead, making it an efficient solution for policy enforcement in large-scale platforms. 🚀  &lt;/p&gt;

&lt;p&gt;You can see the complete example of this script in this &lt;a href="https://github.com/insomnius/tools/tree/master/examples/perceptualhash-ecommerce" rel="noopener noreferrer"&gt;github repository&lt;/a&gt;.&lt;/p&gt;




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

&lt;p&gt;By implementing &lt;strong&gt;perceptual hashing&lt;/strong&gt;, Unicorn Scaled Company significantly improved its ability to detect &lt;strong&gt;policy-violating products&lt;/strong&gt; based on images. Our approach:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Detected sellers re-uploading banned products with modifications.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Worked efficiently at scale with Golang and Kubernetes.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Reduced manual review efforts for compliance teams.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;This solution became a key part of &lt;strong&gt;fraud detection system&lt;/strong&gt;, enforcing policies while keeping the marketplace safe.&lt;/p&gt;




&lt;h2&gt;
  
  
  What should I write next?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Technical details on how pHash works.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combining pHash with computer vision for deeper analysis.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implementing the product detection workflow in Golang.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which one you like to know more? Let’s discuss in the comment sections! 🚀&lt;/p&gt;

</description>
      <category>go</category>
      <category>backend</category>
      <category>programming</category>
      <category>security</category>
    </item>
    <item>
      <title>Think Twice Before Migrating Between PostgreSQL and MySQL</title>
      <dc:creator>Muhammad Arief Rahman</dc:creator>
      <pubDate>Sat, 29 Mar 2025 03:21:45 +0000</pubDate>
      <link>https://dev.to/insomnius/think-twice-before-migrating-between-postgresql-and-mysql-2joh</link>
      <guid>https://dev.to/insomnius/think-twice-before-migrating-between-postgresql-and-mysql-2joh</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp300ihq8lcg4unm6vpl1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp300ihq8lcg4unm6vpl1.png" alt="an Image about inside the HDD" width="720" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@abject" rel="noopener noreferrer"&gt;benjamin lehman&lt;/a&gt; on &lt;a href="https://unsplash.com/" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Hidden Trade-Offs in Database Migration
&lt;/h2&gt;

&lt;p&gt;Switching from MySQL to PostgreSQL or the other way around might seem like an upgrade, but have you considered the trade-offs? One of the biggest factors affecting database performance and consistency is &lt;strong&gt;isolation levels&lt;/strong&gt;. These determine how transactions interact and help balance &lt;strong&gt;data consistency and performance&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you are thinking about switching databases, it is important to understand how different isolation levels impact your data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Isolation Level
&lt;/h2&gt;

&lt;p&gt;The SQL standard defines four isolation levels: &lt;strong&gt;READ UNCOMMITTED&lt;/strong&gt;, &lt;strong&gt;READ COMMITTED&lt;/strong&gt;, &lt;strong&gt;REPEATABLE READ&lt;/strong&gt;, and &lt;strong&gt;SERIALIZABLE&lt;/strong&gt;. These levels progressively increase in strictness, addressing different types of anomalies that can occur during concurrent database access.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Isolation Level \ Read phenomenon&lt;/th&gt;
&lt;th&gt;Dirty Read&lt;/th&gt;
&lt;th&gt;Non Repeatable Read&lt;/th&gt;
&lt;th&gt;Phantom Read&lt;/th&gt;
&lt;th&gt;Performance Rank&lt;/th&gt;
&lt;th&gt;Consistency Rank&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read Uncommitted&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read Committed&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeatable Read&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serializable&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;READ UNCOMMITTED&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Reads uncommitted data, which can lead to &lt;strong&gt;dirty reads&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  Fastest but least reliable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;READ COMMITTED&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Blocks dirty reads but still allows &lt;strong&gt;non-repeatable&lt;/strong&gt; and &lt;strong&gt;phantom reads&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  A balanced choice for performance and accuracy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;REPEATABLE READ&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Prevents dirty and non-repeatable reads, but &lt;strong&gt;phantom reads&lt;/strong&gt; can still happen.&lt;/li&gt;
&lt;li&gt;  Best when reading the same rows multiple times within a transaction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;SERIALIZABLE&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Eliminates dirty reads, non-repeatable reads, and phantom reads.&lt;/li&gt;
&lt;li&gt;  Ensures maximum accuracy but comes with a &lt;strong&gt;performance cost&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Database Anomalies
&lt;/h2&gt;

&lt;p&gt;Database anomalies refer to inconsistencies or unexpected behaviors that can occur when multiple transactions interact concurrently under different isolation levels. These anomalies are primarily caused by the lack of appropriate isolation between transactions. The main types of database anomalies are &lt;em&gt;dirty reads&lt;/em&gt;, &lt;em&gt;non-repeatable reads&lt;/em&gt;, and &lt;em&gt;phantom reads&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dirty reads
&lt;/h3&gt;

&lt;p&gt;A dirty read (aka &lt;em&gt;uncommitted dependency&lt;/em&gt;) occurs when a transaction retrieves a row that has been updated by another transaction that is not yet committed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flyiya2dva0cihvrhba6b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flyiya2dva0cihvrhba6b.png" alt="SQL transaction example showing how different isolation levels handle dirty reads. Transaction 1 reads a balance while Transaction 2 updates and rolls back, demonstrating how Read Uncommitted allows dirty reads while others do not." width="788" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Non-repeatable reads
&lt;/h3&gt;

&lt;p&gt;A non-repeatable read occurs when a transaction retrieves a row twice and that row is updated by another transaction that is committed in between.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr0c0hnlfg4iyqacl8mfb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr0c0hnlfg4iyqacl8mfb.png" alt="SQL transaction example illustrating non-repeatable reads. Transaction 1 reads a balance while Transaction 2 updates and commits, causing Read Uncommitted and Read Committed to show different values, while Repeatable Read and Serializable prevent the issue." width="752" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Phantom reads
&lt;/h3&gt;

&lt;p&gt;A phantom read occurs when a transaction retrieves a set of rows twice and new rows are inserted into or removed from that set by another transaction that is committed in between.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl5ckqs0n47ho3zru8r2d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl5ckqs0n47ho3zru8r2d.png" alt="SQL transaction example demonstrating phantom reads. Transaction 1 queries a dataset while Transaction 2 inserts a new row and commits, causing Read Uncommitted, Read Committed, and Repeatable Read to return different results, while Serializable prevents the issue." width="766" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Default Isolation Level of Popular Database
&lt;/h2&gt;

&lt;p&gt;The following table outlines the default isolation levels for popular databases, detailing their behaviors, purposes, and sources.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Default Isolation Level&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Details&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Source&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MySQL (InnoDB)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Repeatable Read&lt;/td&gt;
&lt;td&gt;Ensures consistent reads for the duration of a transaction.&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html" rel="noopener noreferrer"&gt;MySQL Documentation&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Read Committed&lt;/td&gt;
&lt;td&gt;Only sees data committed before the transaction began.&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.postgresql.org/docs/current/transaction-iso.html" rel="noopener noreferrer"&gt;PostgreSQL Documentation&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SQL Server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Read Committed&lt;/td&gt;
&lt;td&gt;Uses row versioning to achieve consistency.&lt;/td&gt;
&lt;td&gt;&lt;a href="https://learn.microsoft.com/en-us/sql/t-sql/statements/set-transaction-isolation-level-transact-sql" rel="noopener noreferrer"&gt;SQL Server Documentation&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Oracle&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Read Committed&lt;/td&gt;
&lt;td&gt;Provides a snapshot of data committed before the query.&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/SET-TRANSACTION.html" rel="noopener noreferrer"&gt;Oracle Documentation&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SQLite&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Serializable (via Write-Ahead Logging)&lt;/td&gt;
&lt;td&gt;Strong consistency by locking entire database.&lt;/td&gt;
&lt;td&gt;&lt;a href="https://sqlite.org/isolation.html" rel="noopener noreferrer"&gt;SQLite Documentation&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How This Affects Database Migration
&lt;/h2&gt;

&lt;p&gt;Migrating between MySQL and PostgreSQL is not as simple as exporting and importing data. One major difference is how each database &lt;strong&gt;handles isolation levels by default&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;PostgreSQL uses READ COMMITTED by default.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;MySQL uses REPEATABLE READ by default.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your application expects transactions to behave the same way after migration, you might run into unexpected issues. What worked fine in one database could fail or behave unpredictably in another.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for You
&lt;/h2&gt;

&lt;p&gt;Before switching databases, take the time to analyze how transaction isolation affects your &lt;strong&gt;application logic, data integrity, and performance&lt;/strong&gt;. Migrating without considering these differences could lead to &lt;strong&gt;serious issues&lt;/strong&gt; down the road.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key takeaway:&lt;/strong&gt; Know how your database handles transactions before making the switch.&lt;/p&gt;

&lt;p&gt;Want more insights on database performance, transaction handling, and software engineering? Follow me for updates!&lt;/p&gt;

&lt;h2&gt;
  
  
  Explore More In-Depth Insights
&lt;/h2&gt;

&lt;p&gt;This article covers key transaction isolation concepts, but there is much more to learn about database consistency, performance trade-offs, and real-world implementations. If you found this helpful, check out the &lt;strong&gt;full, in-depth article&lt;/strong&gt; on my website:&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://insomnius.dev/blog/building-go-wallet-app-with-redis-like-single-threaded-event-loop" rel="noopener noreferrer"&gt;&lt;strong&gt;Building a Go Wallet App with a Redis-Like Single-Threaded Event Loop&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On &lt;a href="https://insomnius.dev" rel="noopener noreferrer"&gt;&lt;strong&gt;Insomnius.dev&lt;/strong&gt;&lt;/a&gt;, I write about:&lt;br&gt;
✅ Database performance tuning&lt;br&gt;
✅ Transaction isolation deep dives&lt;br&gt;
✅ Scalable backend architecture&lt;br&gt;
✅ Real-world implementation guides&lt;/p&gt;

</description>
      <category>postgressql</category>
      <category>mysql</category>
      <category>database</category>
    </item>
    <item>
      <title>Automated Deployment of Golang Applications to VM with Gitlab CI/CD</title>
      <dc:creator>Muhammad Arief Rahman</dc:creator>
      <pubDate>Sat, 29 Mar 2025 03:05:04 +0000</pubDate>
      <link>https://dev.to/insomnius/automated-deployment-of-golang-applications-to-vm-with-gitlab-cicd-3b8i</link>
      <guid>https://dev.to/insomnius/automated-deployment-of-golang-applications-to-vm-with-gitlab-cicd-3b8i</guid>
      <description>&lt;p&gt;This article will discuss the practice of deployment using GitLab Pipeline with a Continuous Integration/Continuous Deployment (CI/CD) approach. My goal in creating this article is to facilitate indie hackers and organizations in implementing CI/CD in their applications, as well as to share what I use and apply in the applications I create.&lt;/p&gt;

&lt;p&gt;To facilitate the delivery of the material, I will provide a perspective where we are part of a team with a messy deployment and development flow. The team currently faces several problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deployment Process Takes Too Long&lt;/strong&gt;: The deployment process takes more than 10 minutes due to manual steps performed by engineers or DevOps. For example, DevOps needs to manually deploy your code using FTP or similar server synchronization tools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Discrepancies Between the Production and Other Environments&lt;/strong&gt;: The application running in the production environment significantly differs from what is in the local or staging environment. Sometimes, issues that occur in the production application do not happen when the application is running in the local or staging environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Making Bug Fixing and Testing Processes Very Difficult&lt;/strong&gt;: There are limitations in the involvement of engineers in the deployment process. This results in a heavy reliance on the DevOps team, leading to a single point of failure when DevOps is unavailable for deployment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Difficulty in Performing Rollbacks&lt;/strong&gt;: When errors occur in the newly deployed application, the rollback process takes a considerable amount of time and is prone to human errors.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Roughly, if we were to visualize it, the deployment process flowchart within the team would look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyq9dstlh8hwc34uyiose.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyq9dstlh8hwc34uyiose.png" alt="Image description" width="720" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the graph above, we can see that each engineer performs development locally and merges their code into a code repository with good version control. Afterward, the responsibility for deployment falls entirely on the DevOps or sysadmin team. The deployment process also involves using FTP to transfer all files to the server, and sysadmins manually check the code one by one without knowing whether the code is suitable for deployment or not.&lt;/p&gt;

&lt;p&gt;From the graph, what can be improved? Imagine if the deployment process could be made easy, with just a click of a button triggering automatic testing for all changes made by engineers, and the deployment process no longer required DevOps because the application synchronization on the server was also automated.&lt;/p&gt;

&lt;p&gt;Then, we wouldn’t have to waste time worrying about deployment. DevOps would have more time to focus on comprehensive system improvements without sacrificing their time to deploy code whose significance they may not fully understand. Engineers could also directly participate in the deployment process, broadening their knowledge base and fostering a strong sense of ownership over the code they create.&lt;/p&gt;

&lt;p&gt;From these identified issues, these are the four actions we will take:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automate the Deployment Process&lt;/strong&gt;: We will automate the deployment process, eliminating the need for DevOps intervention and reducing deployment time to less than 10 minutes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automated Application Testing (Self-Test Application)&lt;/strong&gt;: We will enable the application to test itself and perform automated quality checks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Utilize Containers&lt;/strong&gt;: To minimize code drift, we will use Docker containers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Efficient Rollback System&lt;/strong&gt;: In case of deployment errors, we will implement an efficient rollback mechanism for quick mitigation.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These steps will help streamline the deployment process, enhance application quality, and provide a more efficient and reliable deployment pipeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqx34nxdfm3t0u2n4wyc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqx34nxdfm3t0u2n4wyc.png" alt="Image description" width="720" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well, more or less, what we are going to create is as shown in the above diagram. With the new flow, we have eliminated Moeldoko’s (DevOps) sole responsibility as the application deployer to production, and the deployment process is now fully automated by GitLab Pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things you need before proceeding
&lt;/h2&gt;

&lt;p&gt;To follow the practices in this article, make sure you have the following tools in your local environment:&lt;/p&gt;

&lt;p&gt;Here’s the translation of your requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;git:&lt;/strong&gt; We will use this as our version control tool.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;docker&lt;/strong&gt;: We will use Docker to build our containers locally.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;go&lt;/strong&gt;: In this article, we use Go as an example application, so you need to install Go.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;wsl2 (if using Windows)&lt;/strong&gt;: If you are using Windows, you should use WSL2 and install all your dependencies within WSL.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Gitlab&lt;/strong&gt;: To follow the next steps, make sure you have an account on GitLab.com.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;VM&lt;/strong&gt;: Of course, you need to prepare a VM, whether it’s on your local machine or with a cloud provider. I personally recommend trying it on a cloud VM to get a more realistic use case experience.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By the way, a disclaimer before we proceed further. The step-by-step tutorial I’ve written in this article uses Ubuntu within the WSL system, and I will also be using commands that are available within that operating system. If you are using a different operating system, please adjust accordingly to your needs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 1: Create a Golang Application
&lt;/h1&gt;

&lt;p&gt;In the first step, we will create a Golang application. The application we are going to create is a simple one with one endpoint and includes unit tests. If you already have your own Golang application and you want to use it for this example, you can skip to the second step.&lt;/p&gt;

&lt;p&gt;First, run the script below to create a new directory and create the main.go file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir golang-cicd &amp;amp;&amp;amp; cd golang-cicd
echo 'package main\\n\\nfunc main() {\\n\\n}' &amp;gt; main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, initialize the Golang package by running the following command, don’t forget to replace ‘alianarib’ with your GitLab username:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go mod init gitlab.com/alianarib/gocicd
go mod tidy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize the Git repository with ‘&lt;strong&gt;main&lt;/strong&gt;’ as your main branch using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git init
git checkout -b main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new folder inside the project folder named ‘&lt;strong&gt;handler&lt;/strong&gt;’.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Then create a file named ‘handler.go’ inside the ‘&lt;strong&gt;handler/&lt;/strong&gt;’ folder.&lt;br&gt;
&lt;/p&gt;

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

import (
 "fmt"
 "net/http"
 "os"
)

func Hello(w http.ResponseWriter, r \*http.Request) {
 fmt.Fprintln(w, "Hello, World! My name is:", os.Getenv("MYNAME"))
}

func Goodbye(w http.ResponseWriter, r \*http.Request) {
 fmt.Fprintln(w, "Goodbye, World! My name is:", os.Getenv("MYNAME"))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ‘&lt;strong&gt;handler&lt;/strong&gt;’ package is used to store all the HTTP handlers we will use. In this file, we also utilize an environment variable called ‘&lt;strong&gt;MYNAME&lt;/strong&gt;,’ which will be automatically filled with the value we declare in our respective environment variables.&lt;/p&gt;

&lt;p&gt;Next, create unit tests in the ‘&lt;strong&gt;handler_test.go&lt;/strong&gt;’ file inside the ‘&lt;strong&gt;handler/&lt;/strong&gt;’ folder.&lt;br&gt;
&lt;/p&gt;

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

import (
 "fmt"
 "net/http"
 "net/http/httptest"
 "os"
 "testing"
)

func TestHelloHandler(t \*testing.T) {
 os.Setenv("MYNAME", "the hash slinging slasher")
 req, err := http.NewRequest("GET", "/hello", nil)
 if err != nil {
  t.Fatal(err)
 }

 recorder := httptest.NewRecorder()
 handler := http.HandlerFunc(Hello)

 handler.ServeHTTP(recorder, req)

 if status := recorder.Code; status != http.StatusOK {
  t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
 }

 expected := fmt.Sprintf("Hello, World! My name is: %s\\n", os.Getenv("MYNAME"))
 if recorder.Body.String() != expected {
  t.Errorf("handler returned unexpected body: got %v want %v", recorder.Body.String(), expected)
 }
}

func TestGoodbyeHandler(t \*testing.T) {
 os.Setenv("MYNAME", "the hash slinging slasher")

 req, err := http.NewRequest("GET", "/goodbye", nil)
 if err != nil {
  t.Fatal(err)
 }

 recorder := httptest.NewRecorder()
 handler := http.HandlerFunc(Goodbye)

 handler.ServeHTTP(recorder, req)

 if status := recorder.Code; status != http.StatusOK {
  t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
 }

 expected := fmt.Sprintf("Goodbye, World! My name is: %s\\n", os.Getenv("MYNAME"))
 if recorder.Body.String() != expected {
  t.Errorf("handler returned unexpected body: got %v want %v", recorder.Body.String(), expected)
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These unit tests will be used as part of continuous integration, specifically as a component to make our application a self-test application. Next, modify your ‘&lt;strong&gt;main.go&lt;/strong&gt;’ file to look like the following:&lt;br&gt;
&lt;/p&gt;

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

import (
 "fmt"
 "net/http"
 "time"

 "gitlab.com/alianarib/gocicd/handler"
)

func main() {
 http.HandleFunc("/hello", handler.Hello)
 http.HandleFunc("/goodbye", handler.Goodbye)

 server := &amp;amp;http.Server{
  Addr:         ":8080",
  Handler:      http.DefaultServeMux,
  ReadTimeout:  10 \* time.Second, // Set a reasonable read timeout
  WriteTimeout: 10 \* time.Second, // Set a reasonable write timeout
 }

 fmt.Println("Server started at :8080")
 if err := server.ListenAndServe(); err != nil {
  panic(err) // Handle the error appropriately
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;strong&gt;main&lt;/strong&gt; function, several operations are performed, including registering the '&lt;strong&gt;Hello&lt;/strong&gt;' and '&lt;strong&gt;Goodbye&lt;/strong&gt;' handlers we created earlier and running an HTTP server on port 8080. If you have successfully followed all the instructions above, your folder and file structure will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── go.mod
├── handler
│   ├── handler.go
│   └── handler\_test.go
└── main.go

1 directory, 4 files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, try running the application using the command &lt;code&gt;MYNAME=arib go run main.go&lt;/code&gt;. The application will then run on port 8080.&lt;/p&gt;

&lt;p&gt;If everything is running smoothly, go ahead and commit to your current ‘&lt;strong&gt;main&lt;/strong&gt;’ branch.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 2: Create a Dockerfile and docker-compose.yaml
&lt;/h1&gt;

&lt;p&gt;In this step, we will create a Dockerfile and docker-compose.yaml as part of the deployment to production using Docker. The aim is to reduce code drift, which is what we want to achieve.&lt;/p&gt;

&lt;p&gt;First, what you need to do is create a Dockerfile in the root directory of your project. Generally, here is the common configuration used for Golang applications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\# We will use the official Golang Docker image
FROM golang:1.20-alpine AS builder

\# Set the /app folder as the working directory
\# All subsequent commands will be executed in this folder
WORKDIR /app

\# Copy all files from the current directory
\# where we run the docker build command
\# into the /app folder in the Docker image
COPY . /app/

\# Build the application within the Docker image
RUN GOOS=linux GOARCH=amd64 CGO\_ENABLED=0 go build -o gocicd main.go

\# Declare the final step
\# Still use the official Golang Docker image
FROM golang:1.20-alpine

\# Copy the build result from the previous step
\# into the /usr/local/bin directory
COPY --from=builder ./app/ /usr/local/bin

\# Declare the command to be executed when
\# we run the 'docker run' command
ENTRYPOINT \["gocicd"\]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this file, we declare two steps: the builder step and the final step. The builder step is used to build the Golang application, and the second step copies the build output into the Docker image. (If you have any questions related to Docker, please comment in the comments section.)&lt;/p&gt;

&lt;p&gt;If you are using your own application, you will need to make adjustments according to what your application requires.&lt;/p&gt;

&lt;p&gt;Next, we will try to build a Docker image locally to ensure that the Dockerfile specification we created is correct. Run the following command to build it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t gocicd:latest .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If successful, the output should look like this:&lt;/p&gt;

&lt;p&gt;Next, we will create a docker-compose.yaml file in the &lt;strong&gt;deployment/production&lt;/strong&gt; folder. If you have more than one environment, you can define them as follows, for example: &lt;strong&gt;deployment/staging&lt;/strong&gt;. The content of the docker-compose.yaml file is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3'
services:
  gocicd:
    container\_name: gocicd
    image: registry.gitlab.com/alianarib/gocicd:${TAG}
    env\_file: .env
    logging:
      driver: "json-file"
      options:
        max-size: "200k"
        max-file: "10"
        mode: non-blocking
    restart: always

  nginx-proxy:
    image: nginx:1.25.1
    container\_name: nginx-proxy
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends\_on:
      - gocicd
    logging:
      driver: "json-file"
      options:
        max-size: "200k"
        max-file: "10"
        mode: non-blocking
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can replace the ‘alianarib’ value with your GitLab username. In the docker-compose.yaml file above, we define two containers to run:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Our application&lt;/li&gt;
&lt;li&gt; Nginx as the web server&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since we are using Nginx, we will define the nginx.conf file according to our needs. To do this, we need to create a new file in deployment/production/nginx.conf, and the content should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;events {
  worker\_connections 1024;
}

http {
  include /etc/nginx/mime.types;
  default\_type application/octet-stream;

  log\_format main '$remote\_addr - $remote\_user \[$time\_local\] "$request" '
  '$status $body\_bytes\_sent "$http\_referer" '
  '"$http\_user\_agent" "$http\_x\_forwarded\_for"';

  access\_log /var/log/nginx/access.log main;
  error\_log /var/log/nginx/error.log;
  sendfile on;

  keepalive\_timeout 65;
  gzip on;

  server {
    listen 80;

    location / {
      proxy\_pass http://gocicd:8080;
      proxy\_set\_header X-Forwarded-For $remote\_addr;
      proxy\_set\_header Host $http\_host;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After writing the above files, make sure your folder and file structure is the same as the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── Dockerfile
├── deployment
│   └── production
│       ├── docker-compose.yaml
│       └── nginx.conf
├── go.mod
├── handler
│   ├── handler.go
│   └── handler\_test.go
└── main.go

3 directories, 7 files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don’t forget to commit your changes before moving on to the next step.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 3: GitLab CI/CD
&lt;/h1&gt;

&lt;p&gt;The next step is to create a GitLab pipeline definition according to your needs. The GitLab pipeline we create should follow CI/CD best practices. Whenever there is a code change, the application should be able to perform self-testing, and when a new version of the application is released, the pipeline should automatically deploy to production.&lt;/p&gt;

&lt;p&gt;In this article, I won’t delve into GitLab CI/CD extensively. You can use ChatGPT if you want to know the code I will write below. But in broad strokes, we will make the application perform checks every time there is a merge request and code merged into the master branch. Then, we will automate deployment whenever there are new tags for our application releases.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\# Stages definition
stages:
  - test
  - build
  - before\_deployment
  - deployment

\# Common settings for jobs
.deploy\_template:
  image: ubuntu
  only:
    - tags
    - /^v.\*$/
    - /^rc.\*$/
  stage: before\_deployment
  before\_script:
    - apt update &amp;amp;&amp;amp; apt install openssh-client -y
    - mkdir -p ~/.ssh/
    - echo "$SSH\_PRIVATE\_KEY" &amp;gt;&amp;gt; ~/.ssh/id\_rsa
    - echo "$SSH\_PUBLIC\_KEY" &amp;gt;&amp;gt; ~/.ssh/id\_rsa.pub
    - ssh-keyscan -H $SERVER\_HOST &amp;gt;&amp;gt; ~/.ssh/known\_hosts
    - VERSION=$(echo "$CI\_COMMIT\_REF\_NAME" | sed 's/^v//')
    - chmod 600 ~/.ssh/id\_rsa
    - chmod 600 ~/.ssh/id\_rsa.pub
    - echo "$ENV\_PRODUCTION" &amp;gt;&amp;gt; env-production

\# Jobs
unit-test:
  image: golang:1.20-alpine
  stage: test
  script:
    - go test ./...

sast-check:
  image: golang:1.20-alpine
  stage: test
  before\_script:
    - go install github.com/securego/gosec/v2/cmd/gosec@latest
  script:
    - gosec ./...

build-merge:
  image: docker:20.10.16
  services:
    - docker:20.10.16-dind
  only:
    - main
  stage: build
  before\_script:
    - echo $CI\_REGISTRY\_PASSWORD | docker login -u $CI\_REGISTRY\_USER $CI\_REGISTRY --password-stdin
  script:
    - docker build -t registry.gitlab.com/alianarib/gocicd:latest .
    - docker push registry.gitlab.com/alianarib/gocicd:latest

build:
  image: docker:20.10.16
  services:
    - docker:20.10.16-dind
  only:
    - tags
  stage: build
  before\_script:
    - echo $CI\_REGISTRY\_PASSWORD | docker login -u $CI\_REGISTRY\_USER $CI\_REGISTRY --password-stdin
    - VERSION=$(echo "$CI\_COMMIT\_REF\_NAME" | sed 's/^v//')
  script:
    - docker build -t registry.gitlab.com/alianarib/gocicd:$VERSION .
    - docker push registry.gitlab.com/alianarib/gocicd:$VERSION

update\_docker\_compose:
  extends: .deploy\_template
  script:
    - sed -i "s/^TAG=.\*$/TAG=$VERSION/" env-production
    - scp env-production $SERVER\_USER@$SERVER\_HOST:/$SERVER\_USER/production/.env
    - scp -r $CI\_PROJECT\_DIR/deployment/production/ $SERVER\_USER@$SERVER\_HOST:/$SERVER\_USER/

deploy\_api:
  extends: .deploy\_template
  stage: deployment
  script:
    - ssh $SERVER\_USER@$SERVER\_HOST "cd production &amp;amp;&amp;amp; docker compose up -d"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the GitLab CI/CD configuration above, there are several things we automate, including:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Running unit tests for every merge request, merge, and deployment event.&lt;/li&gt;
&lt;li&gt; Running security tests for every merge request, merge, and deployment event.&lt;/li&gt;
&lt;li&gt; Building Docker images when merging and deploying.&lt;/li&gt;
&lt;li&gt; Automatically synchronizing with the server during the deployment process.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These four points are the bare minimum for a project to have a good CI/CD pipeline. In the future, there are many improvements that can be made, such as adding linting checks to the code we write, deploying to multiple environments (e.g., staging), automating tests for every application deployed to the staging environment, and testing database migrations (if applicable).&lt;/p&gt;

&lt;p&gt;Next, after creating the .gitlab-ci.yml file, we will create a new repository in GitLab. However, do not push your code yet. There are some setup steps we need to follow to ensure our pipeline runs smoothly. If you look at the pipeline definition file, you will see several environment variables, such as &lt;strong&gt;$CI_REGISTRY_PASSWORD&lt;/strong&gt; and &lt;strong&gt;$CI_REGISTRY_USER&lt;/strong&gt;. I will try to explain these variables one by one, and then we will define them in the CI/CD repository settings.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;$CI_REGISTRY_USER&lt;/strong&gt;: Your GitLab username.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;$CI_REGISTRY_PASSWORD&lt;/strong&gt;: GitLab personal access token. You can learn how to obtain this here → &lt;a href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html" rel="noopener noreferrer"&gt;GitLab Personal Access Tokens&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;$ENV_PRODUCTION&lt;/strong&gt;: The environment variable you want to define for the production environment.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;$SERVER_HOST&lt;/strong&gt;: The IP address of the VM host.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;$SERVER_USER&lt;/strong&gt;: The user of the VM host that you will use.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;$SSH_PRIVATE_KEY&lt;/strong&gt;: The private SSH key you will use to connect to the server.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;$SSH_PUBLIC_KEY&lt;/strong&gt;: The public SSH key.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For points 4 and 5, I will explain them in the next step, which is the VM Preparation. So, we will prepare for points 6 and 7 first. If you are using macOS or Linux, you can use the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh-keygen -t rsa -f ssh\_deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There will be an interactive terminal where you are asked to enter the passphrase for the RSA key; for now, leave it empty. After running the above command, there should be two files in your current directory, like this:&lt;/p&gt;

&lt;p&gt;The content of the ‘&lt;strong&gt;ssh_deployment&lt;/strong&gt;’ file will be assigned to &lt;strong&gt;SSH_PRIVATE_KEY&lt;/strong&gt;, and the content of the ‘&lt;strong&gt;ssh_deployment.pub&lt;/strong&gt;’ file will be assigned to &lt;strong&gt;SSH_PUBLIC_KEY&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Next, we will define environment variables for the production server in the CI/CD configuration as follows:&lt;/p&gt;

&lt;p&gt;These values will be used by your application as environment variables on the production server. You can define all of these variables in the &lt;strong&gt;Settings &amp;gt; CI/CD &amp;gt; Variables&lt;/strong&gt; menu in your GitLab repository.&lt;/p&gt;

&lt;p&gt;After defining all the variables, the next step is to create protected tags. You can do this in the &lt;strong&gt;Settings &amp;gt; Repository &amp;gt; Protected Tags&lt;/strong&gt; menu. Protected tags are used to ensure that certain tags can only be created by specific roles.&lt;/p&gt;

&lt;p&gt;After everything is done, we will push to the remote repository. If you have followed everything correctly, there should be one pipeline currently running. You can view it in the ‘&lt;strong&gt;Pipeline&lt;/strong&gt;’ menu.&lt;/p&gt;

&lt;p&gt;The pipeline in the ‘&lt;strong&gt;build-merge&lt;/strong&gt;’ job will build your application into a Docker image and automatically store it in the GitLab container registry. Once it’s done, you can see the container image you successfully built in the ‘&lt;strong&gt;Deploy &amp;gt; Container Registry&lt;/strong&gt;’ menu.&lt;/p&gt;

&lt;p&gt;Alright, at this stage, we’ve implemented the continuous integration part of CI/CD. Get ready for the next step!&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 4: VM Preparation
&lt;/h1&gt;

&lt;p&gt;In this step, we will prepare the VM that will be used as the production environment for our application. Some of the things we will do include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Installing Docker and the Docker Compose plugin.&lt;/li&gt;
&lt;li&gt; Creating a new user for deployment purposes.&lt;/li&gt;
&lt;li&gt; Adding the previously generated public key to the authorized_keys list.&lt;/li&gt;
&lt;li&gt; Creating a ‘deployment’ folder.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are additional steps that are generally good practice but are not included here to keep this article concise:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Creating a new non-root user for deployment.&lt;/li&gt;
&lt;li&gt; Disabling password-based login on the VM.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Disclaimer: This is not a promotion, but for the VM, I will choose to use Vultr because it is affordable and straightforward. However, if you feel uncomfortable with Vultr, please use another VM provider, but make sure the VM can be accessed for SSH login (consider providers like GCP and Azure).&lt;/p&gt;

&lt;p&gt;Next, if you have already provisioned the VM, we will log in to the VM using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh root@your.server.ip.address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After logging into the VM, you need to install all the required dependencies, which are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Docker&lt;/li&gt;
&lt;li&gt; Docker Compose&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To install Docker on the VM, you can follow this guide: &lt;a href="https://docs.docker.com/engine/install/ubuntu/" rel="noopener noreferrer"&gt;Docker Installation on Ubuntu&lt;/a&gt;. I haven’t provided the installation steps here because the method of installing Docker may change over time.&lt;/p&gt;

&lt;p&gt;After successfully installing Docker, the next step is to create a ‘&lt;strong&gt;deployment&lt;/strong&gt;’ folder. You can do this with the following command:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;By using that command, you will create a new ‘&lt;strong&gt;production&lt;/strong&gt;’ folder in the &lt;strong&gt;/root/ directory&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;After everything is set up, don’t forget to add the &lt;strong&gt;SERVER_HOST&lt;/strong&gt; and &lt;strong&gt;SERVER_USER&lt;/strong&gt; variables in your CI/CD config variables with the host and user you want to use for deployment.&lt;/p&gt;

&lt;p&gt;If you’re done, then we’re moving on to the next step, which is deployment!&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 5: Deployment
&lt;/h1&gt;

&lt;p&gt;Now that everything is set up, the final step is deployment. Because we have a CI/CD pipeline in place, the deployment process can be done easily by simply creating a GitLab tag. This is the continuous delivery stage that we will try to apply to the application we created earlier. You can create a GitLab tag in the ‘&lt;strong&gt;Code &amp;gt; Tags&lt;/strong&gt;’ menu.&lt;/p&gt;

&lt;p&gt;When you create a GitLab tag, an automatic pipeline will run and perform the deployment to your server.&lt;/p&gt;

&lt;p&gt;If the pipeline is all green, then check your server to ensure everything is running as it should using the ‘docker ps’ command to see all running containers.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;If your application is running smoothly, the output should look like this.&lt;/p&gt;

&lt;p&gt;The image above shows that there are 2 containers running. Next, we will try to access the application using the public IP.&lt;/p&gt;

&lt;p&gt;Well, if the output appears as shown above, you can be sure that your application is running fine. Now, let’s pretend there is a code change in our application and see the changes live. First, open the ‘&lt;strong&gt;handler.go&lt;/strong&gt;’ file and change the word ‘&lt;strong&gt;world&lt;/strong&gt;’ to ‘&lt;strong&gt;Indonesia&lt;/strong&gt;’.&lt;br&gt;
&lt;/p&gt;

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

import (
 "fmt"
 "net/http"
 "os"
)

func Hello(w http.ResponseWriter, r \*http.Request) {
 fmt.Fprintln(w, "Hello, Indonesia! My name is:", os.Getenv("MYNAME"))
}

func Goodbye(w http.ResponseWriter, r \*http.Request) {
 fmt.Fprintln(w, "Goodbye, Indonesia! My name is:", os.Getenv("MYNAME"))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don’t forget to change the test as well. After that, we will push our code and create a new tag:&lt;/p&gt;

&lt;p&gt;Next, we will try to access the API that we modified earlier.&lt;/p&gt;

&lt;p&gt;Up to this point, the deployment pipeline is working well. But what if in the next deployment there is an error, and we need to mitigate that error quickly? We will discuss that in the next step.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 6: Rollback
&lt;/h1&gt;

&lt;p&gt;In this part, we will discuss what we will do if there is an error in the latest deployment and we need to mitigate it quickly.&lt;/p&gt;

&lt;p&gt;To simulate an error, we will use the ‘&lt;strong&gt;panic&lt;/strong&gt;’ function in Golang in the ‘&lt;strong&gt;handler.go&lt;/strong&gt;’ file. You can add it as follows:&lt;/p&gt;

&lt;p&gt;What the code above does is trigger a panic when there is a ‘test’ query parameter in the URL ‘/hello’. After that, we will create a new deployment tag and see the result directly in production.&lt;/p&gt;

&lt;p&gt;Well, it turns out that after the new deployment, an unwanted error occurred. Let’s pretend that fixing it will take a very long time, and many users have been affected by this error. So, how do we mitigate it? The mitigation is to trigger the deployment pipeline of the previous version again.&lt;/p&gt;

&lt;p&gt;And when we check again, our application will return to the previous version, where there was no error or panic.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Before I write the conclusion, I want to express my gratitude and congratulations for following the step-by-step instructions in this article. I hope you’ve gained a broad understanding of how CI/CD can be implemented. Of course, this application is not perfect, and there are areas that can be improved, such as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Storing environment variables in the GitLab file can be risky for companies with a relatively large number of employees and challenging access control. Therefore, safer alternatives like using HashiCorp’s Vault or similar technologies are recommended.&lt;/li&gt;
&lt;li&gt; Implementing a secure VM practice is highly recommended. You should set it up in a way that is far more secure than what was depicted in this article.&lt;/li&gt;
&lt;li&gt; For more secure implementation, you can use your own gitlab runner.&lt;/li&gt;
&lt;li&gt; Adding SSL certificates.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In essence, CI/CD aims to make the software development lifecycle better and faster through automation. All repetitive and time-consuming tasks should be automated, and engineers should be involved in all development layers to become more aware of the applications they build.&lt;/p&gt;

&lt;h1&gt;
  
  
  What’s Next?
&lt;/h1&gt;

&lt;p&gt;So what’s next after this? You can further expand your CI/CD setup, for example, by moving to Kubernetes instead of VMs. You can also explore creating your dedicated GitLab runners. Happy development, happy deployment!&lt;/p&gt;

&lt;p&gt;If you have any topics you’d like me to discuss, please leave a comment below. Thank you for reading this until the end. The article creation process took a bit longer because I made revisions here and there to make it easy to read.&lt;/p&gt;

&lt;p&gt;Please follow me, and don’t forget to give it a clap! Thanks!!&lt;/p&gt;

&lt;h1&gt;
  
  
  Links
&lt;/h1&gt;

&lt;p&gt;The GitLab repository related to this project: &lt;a href="https://gitlab.com/alianarib/gocicd" rel="noopener noreferrer"&gt;https://gitlab.com/alianarib/gocicd&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>cicd</category>
      <category>gitlab</category>
      <category>docker</category>
    </item>
  </channel>
</rss>
