<?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: Andrew Kang-G</title>
    <description>The latest articles on DEV Community by Andrew Kang-G (@andrewkangg).</description>
    <link>https://dev.to/andrewkangg</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%2F174931%2F74d366bf-6b9f-4b1e-be90-bdc815023cfe.JPG</url>
      <title>DEV Community: Andrew Kang-G</title>
      <link>https://dev.to/andrewkangg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andrewkangg"/>
    <language>en</language>
    <item>
      <title>An Isomorphic Blue-Green Deployment Starting from Your Source Code—Not from Your Prebuilt Docker Image</title>
      <dc:creator>Andrew Kang-G</dc:creator>
      <pubDate>Tue, 30 Sep 2025 07:50:18 +0000</pubDate>
      <link>https://dev.to/andrewkangg/an-isomorphic-blue-green-deployment-starting-from-your-source-code-not-from-your-prebuilt-docker-43e5</link>
      <guid>https://dev.to/andrewkangg/an-isomorphic-blue-green-deployment-starting-from-your-source-code-not-from-your-prebuilt-docker-43e5</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/patternhelloworld/docker-blue-green-runner" rel="noopener noreferrer"&gt;https://github.com/patternhelloworld/docker-blue-green-runner&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Achieve zero-downtime deployment using just your .env and Dockerfile
Docker-Blue-Green-Runner's run.sh script is designed to simplify deployment: "With your .env, project, and a single Dockerfile, simply run 'bash run.sh'." If you prefer not to use sudo, see WITH_SUDO, set it in your .env, and run apply-security.sh first. This script covers the entire process from Dockerfile build to server deployment from scratch.
This means you can easily migrate to another server with just the files mentioned above.
In contrast, Traefik requires the creation and gradual adjustment of various configuration files, which requires your App's docker binary running.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>docker</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Production deployment</title>
      <dc:creator>Andrew Kang-G</dc:creator>
      <pubDate>Mon, 29 Sep 2025 15:31:44 +0000</pubDate>
      <link>https://dev.to/andrewkangg/production-deployment-c5l</link>
      <guid>https://dev.to/andrewkangg/production-deployment-c5l</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/patternhelloworld/docker-blue-green-runner?tab=readme-ov-file#features" rel="noopener noreferrer"&gt;https://github.com/patternhelloworld/docker-blue-green-runner?tab=readme-ov-file#features&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>docker</category>
      <category>java</category>
      <category>laravel</category>
    </item>
    <item>
      <title>Some Tips on SQL Usage in Spring</title>
      <dc:creator>Andrew Kang-G</dc:creator>
      <pubDate>Tue, 17 Jun 2025 16:29:26 +0000</pubDate>
      <link>https://dev.to/andrewkangg/correct-sql-usage-in-spring-53e0</link>
      <guid>https://dev.to/andrewkangg/correct-sql-usage-in-spring-53e0</guid>
      <description>&lt;h1&gt;
  
  
  Table of Contents
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;A Left Join followed by an Inner Join behaves like a regular Inner Join&lt;/li&gt;
&lt;li&gt;When pagination is used, avoid using &lt;code&gt;groupBy&lt;/code&gt; or &lt;code&gt;associateBy&lt;/code&gt; in the app layer – use SQL &lt;code&gt;GROUP BY&lt;/code&gt; instead&lt;/li&gt;
&lt;li&gt;Understand SQL query execution order&lt;/li&gt;
&lt;li&gt;Adding &lt;code&gt;AND&lt;/code&gt; conditions in &lt;code&gt;LEFT JOIN ON&lt;/code&gt; does not affect row count&lt;/li&gt;
&lt;li&gt;For one-to-one relationships, use "@OneToOne" and enforce a UNIQUE KEY in the database&lt;/li&gt;
&lt;li&gt;"@Transactional" only guarantees atomicity in specific situations&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WHERE&lt;/code&gt; vs &lt;code&gt;HAVING&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  1. A Left Join followed by an Inner Join behaves like a regular Inner Join
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;If a regular JOIN is mixed after a LEFT JOIN chain, the following JOIN behaves as an INNER JOIN.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Example
&lt;/h1&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT *
FROM user u
LEFT JOIN order o ON u.id = o.user_id
JOIN product p ON o.product_id = p.id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Since &lt;code&gt;product&lt;/code&gt; is joined with a regular &lt;code&gt;JOIN&lt;/code&gt;, it acts as an &lt;code&gt;INNER JOIN&lt;/code&gt;, so the entire query behaves like an &lt;code&gt;INNER JOIN&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Reference: &lt;a href="https://velog.io/@dlswns2480/LEFT-JOIN-%EC%8B%9C-%EC%A3%BC%EC%9D%98%ED%95%B4%EC%95%BC-%ED%95%A0-%EC%A0%90" rel="noopener noreferrer"&gt;Caution when using LEFT JOIN (Korean)&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  2. Avoid using groupBy/associateBy in the app layer when pagination is applied
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;If you paginate using SQL (LIMIT, OFFSET) and then group data in the app layer using groupBy or associateBy, the number of items per page becomes inconsistent, making pagination meaningless.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Example (Kotlin)
&lt;/h1&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val orders = fetchPageFromDB(limit = 10, offset = 0)
val grouped = orders.groupBy { it.userId }
println(grouped.size) // Result: 6 groups
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;→ Next page:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val orders = fetchPageFromDB(limit = 10, offset = 10)
val grouped = orders.groupBy { it.userId }
println(grouped.size) // Result: 3 groups
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Inconsistent item count per page → user confusion → Pagination should be based on SQL results to &lt;strong&gt;guarantee consistency&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  3. Understand SQL Query Execution Order
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL runs in the following order: FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Understanding this order helps in predicting query results and writing more effective queries.&lt;/p&gt;

&lt;p&gt;It’s recommended to practice by creating small test tables (e.g., 5 rows) with various relationships (1:1, 1:N, N:M) and experimenting with different query combinations to &lt;strong&gt;predict results instead of just executing and checking&lt;/strong&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  4. Adding AND conditions in LEFT JOIN ON does not affect row count
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;If you put conditions in the ON clause with AND in a LEFT JOIN, it doesn't filter rows – it just returns NULL in the joined table if the condition doesn’t match.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To filter rows, you &lt;strong&gt;must use the&lt;/strong&gt; &lt;code&gt;WHERE&lt;/code&gt; &lt;strong&gt;clause&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Some developers overuse &lt;code&gt;AND&lt;/code&gt; thinking it’s more performant than &lt;code&gt;WHERE&lt;/code&gt;,&lt;br&gt;&lt;br&gt;
but in &lt;code&gt;LEFT JOIN&lt;/code&gt;, &lt;code&gt;AND&lt;/code&gt; does not filter base rows.&lt;/p&gt;

&lt;h1&gt;
  
  
  Example 1: LEFT JOIN with ON ... AND
&lt;/h1&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT u.id AS user_id, u.name AS user_name, o.id AS order_id, o.status
FROM users u
LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'DELIVERED';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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

&lt;ul&gt;
&lt;li&gt;Alice has orders with statuses 'DELIVERED' and 'PENDING'.

&lt;ul&gt;
&lt;li&gt;The 'DELIVERED' one is joined.&lt;/li&gt;
&lt;li&gt;The 'PENDING' one shows up as &lt;code&gt;NULL&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Bob and Carol have no matching 'DELIVERED' orders → &lt;code&gt;NULL&lt;/code&gt;.&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Example 2: INNER JOIN with ON ... AND
&lt;/h1&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT u.id AS user_id, u.name AS user_name, o.id AS order_id, o.status
FROM users u
INNER JOIN orders o ON u.id = o.user_id AND o.status = 'DELIVERED';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Only Alice’s 'DELIVERED' order is shown.&lt;/p&gt;

&lt;h1&gt;
  
  
  Example 3: LEFT JOIN with WHERE
&lt;/h1&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT u.id AS user_id, u.name AS user_name, o.id AS order_id, o.status
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.status = 'DELIVERED';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Even though it's a &lt;code&gt;LEFT JOIN&lt;/code&gt;, the &lt;code&gt;WHERE&lt;/code&gt; condition filters rows like an &lt;code&gt;INNER JOIN&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary Table
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Affects Row Count&lt;/th&gt;
&lt;th&gt;When Condition Fails&lt;/th&gt;
&lt;th&gt;Usage Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LEFT JOIN ON ... AND&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;NULL in joined table only&lt;/td&gt;
&lt;td&gt;Keep base table rows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;INNER JOIN ON ... AND&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Row excluded&lt;/td&gt;
&lt;td&gt;Only matched rows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;LEFT JOINWHERE&lt;/code&gt; +   filter&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Row excluded&lt;/td&gt;
&lt;td&gt;Works like INNER JOIN&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  5. For one-to-one relationships, use OneToOne and enforce UNIQUE KEY in DB
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Don’t use ManyToOne for one-to-one mappings.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Kotlin Example
&lt;/h1&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@OneToOne
(name = "profile_id", unique = true)
val profile: UserProfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  6. "@Transactional" only guarantees atomicity in specific situations
&lt;/h1&gt;
&lt;h1&gt;
  
  
  (1) Rollback only occurs for RuntimeException by default
&lt;/h1&gt;

&lt;p&gt;To rollback on &lt;strong&gt;all exceptions&lt;/strong&gt;, specify explicitly:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kotlin@Transactional(rollbackFor = [Exception::class])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  (2) While "@Transactional" is running, other requests are not blocked
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Even inside the same application, transactional behavior depends on the &lt;code&gt;isolation level&lt;/code&gt; set in &lt;a href="http://application.properties" rel="noopener noreferrer"&gt;&lt;code&gt;application.properties&lt;/code&gt;&lt;/a&gt; and the DB.&lt;/li&gt;
&lt;li&gt;In a load-balanced environment, requests may go to multiple instances and concurrently hit the DB.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Example:
&lt;/h1&gt;
&lt;h1&gt;
  
  
  1. Transaction A starts
&lt;/h1&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BEGIN;
SELECT MAX(id) FROM user; -- Result: 100
-- Prepares to insert id = 101
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  2. Transaction B interjects before A commits
&lt;/h1&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BEGIN;
SELECT MAX(id) FROM user; -- Result: 100
INSERT INTO user(id, name) VALUES (101, 'UserB');
COMMIT;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  3. Transaction A tries to insert
&lt;/h1&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT INTO user(id, name) VALUES (101, 'UserA');
--  Primary Key conflict!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Table locks may seem like a solution, but they can lead to &lt;strong&gt;deadlocks&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
That’s why &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; or &lt;code&gt;SEQUENCE&lt;/code&gt; exists.&lt;/p&gt;

&lt;p&gt;The important takeaway is that when multiple users are reading/writing concurrently,&lt;br&gt;&lt;br&gt;
you must understand "transaction isolation level" behavior.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Usually, &lt;code&gt;REPEATABLE READ&lt;/code&gt; is used (must align &lt;a href="http://application.properties" rel="noopener noreferrer"&gt;&lt;code&gt;application.properties&lt;/code&gt;&lt;/a&gt; with DB setting).&lt;/li&gt;
&lt;li&gt;Personally prefer &lt;code&gt;READ COMMITTED&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Great reference article (in Korean): &lt;a href="https://mangkyu.tistory.com/299" rel="noopener noreferrer"&gt;https://mangkyu.tistory.com/299&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  (3) Thus, ensure data integrity with DB constraints + exception handling
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try {&lt;br&gt;
    userRepository.save(User(email = "&lt;a href="mailto:test@example.com"&gt;test@example.com&lt;/a&gt;"))&lt;br&gt;
} catch (e: DataIntegrityViolationException) {&lt;br&gt;
    // Handle duplicate email&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  (4) Why use "@Transactional"(readOnly = true) for SELECT?&lt;br&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;It improves &lt;strong&gt;JPA performance&lt;/strong&gt;, not DB-level performance.&lt;/li&gt;
&lt;li&gt;See: &lt;a href="https://hungseong.tistory.com/74" rel="noopener noreferrer"&gt;https://hungseong.tistory.com/74&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  7. WHERE vs HAVING
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Based on SQL execution order: FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Clause&lt;/th&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Filters On&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;WHERE&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GROUP BY&lt;/code&gt;Before&lt;/td&gt;
&lt;td&gt;Individual rows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HAVING&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GROUP BY&lt;/code&gt;After&lt;/td&gt;
&lt;td&gt;Aggregated groups&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;When using &lt;code&gt;GROUP BY&lt;/code&gt;, you must include columns in &lt;code&gt;SELECT&lt;/code&gt; or wrap them in aggregation functions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WHERE&lt;/code&gt; filters rows &lt;strong&gt;before grouping&lt;/strong&gt;, while &lt;code&gt;HAVING&lt;/code&gt; filters &lt;strong&gt;after grouping&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Example 1: Using WHERE
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT *&lt;br&gt;
FROM order&lt;br&gt;
WHERE status = 'PAID'&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Example 2: Using HAVING&lt;br&gt;
&lt;/h1&gt;
&lt;br&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT user_id, COUNT(&lt;em&gt;) as order_count&lt;br&gt;
FROM order&lt;br&gt;
GROUP BY user_id&lt;br&gt;
HAVING COUNT(&lt;/em&gt;) &amp;gt; 3&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Final Tip&lt;br&gt;
&lt;/h1&gt;

&lt;p&gt;Even when using JPA, copy the generated SQL with &lt;code&gt;?&lt;/code&gt; values from the console, paste them into ChatGPT to fill the parameters,&lt;br&gt;&lt;br&gt;
&lt;strong&gt;run the SQL manually&lt;/strong&gt;, and &lt;strong&gt;observe the actual result&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If possible, try checking &lt;strong&gt;execution plans&lt;/strong&gt; later to assess performance.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>sql</category>
      <category>java</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Best Docker Deployment Starategy</title>
      <dc:creator>Andrew Kang-G</dc:creator>
      <pubDate>Sat, 31 May 2025 14:49:30 +0000</pubDate>
      <link>https://dev.to/andrewkangg/best-docker-deployment-starategy-4enc</link>
      <guid>https://dev.to/andrewkangg/best-docker-deployment-starategy-4enc</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/patternhelloworld/docker-blue-green-runner" rel="noopener noreferrer"&gt;https://github.com/patternhelloworld/docker-blue-green-runner&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Achieve zero-downtime deployment using just your .env and Dockerfile&lt;/p&gt;

&lt;p&gt;Docker-Blue-Green-Runner's run.sh script is designed to simplify deployment: "With your .env, project, and a single Dockerfile, simply run 'bash run.sh'." This script covers the entire process from Dockerfile build to server deployment from scratch.&lt;/p&gt;

&lt;p&gt;This means you can easily migrate to another server with just the files mentioned above.&lt;/p&gt;

&lt;p&gt;In contrast, Traefik requires the creation and gradual adjustment of various configuration files, which requires your App's docker binary running.&lt;/p&gt;

&lt;p&gt;No unpredictable errors in reverse proxy and deployment : Implement safety measures to handle errors caused by your app or Nginx&lt;/p&gt;

&lt;p&gt;If any error occurs in the app or router, deployment is halted to prevent any impact on the existing deployment&lt;/p&gt;

&lt;p&gt;Internal Integrity Check:&lt;/p&gt;

&lt;p&gt;Nginx Router Test Container&lt;/p&gt;

&lt;p&gt;External Integrity Check&lt;/p&gt;

&lt;p&gt;Rollback Procedures&lt;/p&gt;

&lt;p&gt;Additional Know-hows on Docker: Tips and best practices for optimizing your Docker workflow and deployment processes&lt;/p&gt;

&lt;p&gt;For example, Traefik offers powerful dynamic configuration and service discovery; however, certain errors, such as a failure to detect containers (due to issues like unrecognized certificates), can lead to frustrating 404 errors that are hard to trace through logs alone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/76660749/traefik-404-page-not-found-when-use-https" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/76660749/traefik-404-page-not-found-when-use-https&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.traefik.io/t/getting-bad-gateway-404-page-when-supposed-to-route-to-container-port-8443/20398" rel="noopener noreferrer"&gt;https://community.traefik.io/t/getting-bad-gateway-404-page-when-supposed-to-route-to-container-port-8443/20398&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Manipulates NGINX configuration files directly to ensure container accessibility.&lt;/p&gt;

&lt;p&gt;Track Blue-Green status and the Git SHA of your running container for easy monitoring.&lt;/p&gt;

&lt;p&gt;Blue-Green deployment decision algorithm: scoring-based approach&lt;/p&gt;

&lt;p&gt;Run the command bash check-current-status.sh (similar to git status) to view all relevant details&lt;/p&gt;

&lt;p&gt;Security&lt;/p&gt;

&lt;p&gt;Refer to the Security section&lt;/p&gt;

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

&lt;p&gt;Refer to the Production Deployment section&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>laravel</category>
      <category>springboot</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Zero downtime docker for PHP</title>
      <dc:creator>Andrew Kang-G</dc:creator>
      <pubDate>Tue, 28 Jan 2025 16:36:03 +0000</pubDate>
      <link>https://dev.to/andrewkangg/zero-downtime-docker-for-php-56gm</link>
      <guid>https://dev.to/andrewkangg/zero-downtime-docker-for-php-56gm</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/patternhelloworld/docker-blue-green-runner" rel="noopener noreferrer"&gt;https://github.com/patternhelloworld/docker-blue-green-runner&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>docker</category>
    </item>
    <item>
      <title>Parse &amp; Decompose Url efficiently and accurately</title>
      <dc:creator>Andrew Kang-G</dc:creator>
      <pubDate>Sat, 25 Jan 2025 07:50:08 +0000</pubDate>
      <link>https://dev.to/andrewkangg/parse-decompose-url-efficiently-and-accurately-169i</link>
      <guid>https://dev.to/andrewkangg/parse-decompose-url-efficiently-and-accurately-169i</guid>
      <description>&lt;p&gt;Maybe the best solution except for AIs that comsume a lot of memory...&lt;br&gt;
&lt;a href="https://github.com/patternhelloworld/url-knife" rel="noopener noreferrer"&gt;https://github.com/patternhelloworld/url-knife&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>regex</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Support for App-Token based multiple verification approaches, including API calls to the authorization server, direct database validation, and local JWT decoding.</title>
      <dc:creator>Andrew Kang-G</dc:creator>
      <pubDate>Sun, 12 Jan 2025 16:49:29 +0000</pubDate>
      <link>https://dev.to/andrewkangg/support-for-app-token-based-multiple-verification-approaches-including-api-calls-to-the-11bb</link>
      <guid>https://dev.to/andrewkangg/support-for-app-token-based-multiple-verification-approaches-including-api-calls-to-the-11bb</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/andrewkangg" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F174931%2F74d366bf-6b9f-4b1e-be90-bdc815023cfe.JPG" alt="andrewkangg"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/andrewkangg/spring-oauth2-app-token-based-hybrid-token-verification-methods-58i8" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Spring Oauth2 - App-Token based Hybrid Token Verification Methods&lt;/h2&gt;
      &lt;h3&gt;Andrew Kang-G ・ Jan 12&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#springsecurity&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#springboot&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#java&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#oauth&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Spring Oauth2 - App-Token based Hybrid Token Verification Methods</title>
      <dc:creator>Andrew Kang-G</dc:creator>
      <pubDate>Sun, 12 Jan 2025 16:46:55 +0000</pubDate>
      <link>https://dev.to/andrewkangg/spring-oauth2-app-token-based-hybrid-token-verification-methods-58i8</link>
      <guid>https://dev.to/andrewkangg/spring-oauth2-app-token-based-hybrid-token-verification-methods-58i8</guid>
      <description>&lt;p&gt;If you’re struggling to set up a persistence-based OAuth2 module, consider trying&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/patternhelloworld/spring-oauth2-easyplus" rel="noopener noreferrer"&gt;https://github.com/patternhelloworld/spring-oauth2-easyplus&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App-Token based easy OAuth2 implementation built to grow with Spring Boot&lt;/li&gt;
&lt;li&gt;Complete separation of the library and the client (Library : API, Client : DOC, Integration tester)&lt;/li&gt;
&lt;li&gt;Extensible: Supports multiple authorization servers and resource servers with this library.&lt;/li&gt;
&lt;li&gt;Hybrid Resource Servers Token Verification Methods: Support for multiple verification approaches, including API calls to the authorization server, direct database validation, and local JWT decoding.&lt;/li&gt;
&lt;li&gt;Immediate Permission (Authority) Check: Not limited to verifying the token itself, but also ensuring real-time validation of any updates to permissions in the database.&lt;/li&gt;
&lt;li&gt;Authentication management based on a combination of username, client ID, and App-Token : What is an App-Token? An App-Token is a new access token generated each time the same account logs in. If the token values are the same, the same access token is shared.&lt;/li&gt;
&lt;li&gt;Separated UserDetails implementation for Admin and Customer roles as an example. (This can be extended such as Admin, Customer, Seller and Buyer… by implementing UserDetailsServiceFactory)&lt;/li&gt;
&lt;li&gt;Authorization Code Flow with Optional PKCE, Authorization Consent and Single Page Application (XMLHttpRequest)&lt;/li&gt;
&lt;li&gt;ROPC for scenarios where accessing a browser screen on the server is either unavailable or impractical&lt;/li&gt;
&lt;li&gt;Application of Spring Rest Docs, Postman payloads provided&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>springsecurity</category>
      <category>springboot</category>
      <category>java</category>
      <category>oauth</category>
    </item>
    <item>
      <title>App-Token based easy OAuth2 implementation built to grow with Spring Boot</title>
      <dc:creator>Andrew Kang-G</dc:creator>
      <pubDate>Wed, 08 Jan 2025 06:36:53 +0000</pubDate>
      <link>https://dev.to/andrewkangg/app-token-based-easy-oauth2-implementation-built-to-grow-with-spring-boot-16mc</link>
      <guid>https://dev.to/andrewkangg/app-token-based-easy-oauth2-implementation-built-to-grow-with-spring-boot-16mc</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/andrewkangg" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F174931%2F74d366bf-6b9f-4b1e-be90-bdc815023cfe.JPG" alt="andrewkangg"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/andrewkangg/spring-oauth2-easyplus-1pj1" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Spring Oauth2 EasyPlus&lt;/h2&gt;
      &lt;h3&gt;Andrew Kang-G ・ Jan 8&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#springsecurity&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#oauth&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#jwt&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#springboot&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>springsecurity</category>
      <category>springboot</category>
      <category>oauth</category>
    </item>
    <item>
      <title>Why would I use this instead of Traefik for zero-downtime deployment?</title>
      <dc:creator>Andrew Kang-G</dc:creator>
      <pubDate>Sat, 16 Nov 2024 00:50:51 +0000</pubDate>
      <link>https://dev.to/andrewkangg/why-would-i-use-this-instead-of-traefik-for-zero-downtime-deployment-3fh2</link>
      <guid>https://dev.to/andrewkangg/why-would-i-use-this-instead-of-traefik-for-zero-downtime-deployment-3fh2</guid>
      <description>&lt;p&gt;First you need to look into a process summary of Docker-Blue-Green-Runner at this link,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/patternhelloworld/docker-blue-green-runner" rel="noopener noreferrer"&gt;https://github.com/patternhelloworld/docker-blue-green-runner&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following explains why I use Docker-Blue-Green-Runner…&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No Unpredictable Errors in Reverse Proxy and Deployment&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If any error occurs in the app or router, deployment is halted to prevent any impact on the existing deployment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For example, Traefik offers powerful dynamic configuration and service discovery; however, certain errors, such as a failure to detect containers (due to issues like unrecognized certificates), can lead to frustrating 404 errors that are hard to trace through logs alone.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/76660749/traefik-404-page-not-found-when-use-https" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/76660749/traefik-404-page-not-found-when-use-https&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.traefik.io/t/getting-bad-gateway-404-page-when-supposed-to-route-to-container-port-8443/20398" rel="noopener noreferrer"&gt;https://community.traefik.io/t/getting-bad-gateway-404-page-when-supposed-to-route-to-container-port-8443/20398&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Manipulates NGINX configuration files directly to ensure container accessibility. It also tests configuration files by launching a test NGINX Docker instance, and if an NGINX config update via Consul-Template fails, Contingency Plan provided is activated to ensure connectivity to your containers.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;From Scratch&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Docker-Blue-Green-Runner’s run.sh script is designed to simplify deployment: "With your .env, project, and a single Dockerfile, simply run 'bash run.sh'." This script covers the entire process from Dockerfile build to server deployment from scratch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This means you can easily migrate to another server with just the files mentioned above.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In contrast, Traefik requires the creation and gradual adjustment of various configuration files, which can introduce the types of errors mentioned above.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Focus on zero-downtime deployment on a single machine.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;While Kubernetes excels in multi-machine environments with the support of Layer 7 (L7) technologies (I would definitely use Kubernetes in that case), this approach is ideal for scenarios where only one or two machines are available.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;However, for deployments involving more machines, traditional Layer 4 (L4) load-balancer using servers could be utilized.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let’s dive into how to use it…&lt;br&gt;
First, clone the project&lt;/p&gt;

&lt;p&gt;git clone &lt;a href="https://github.com/patternhelloworld/docker-blue-green-runner" rel="noopener noreferrer"&gt;https://github.com/patternhelloworld/docker-blue-green-runner&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Create an .env file at the root of the project.&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;  # Leave as it is
  HOST_IP=host.docker.internal

  # It is recommended to enter your formal URL or IP, such as https://test.com, for the 'check_availability_out_of_container' test in the 'run.sh' script.
  # Both https://your-app.com:443 and https://localhost:443 are valid
  # Docker-Blue-Runner recognizes if your App requires SSL in the Nginx router if this starts with 'https'.
  # This URL is used for the "External Integrity Check" process.
  APP_URL=https://localhost:443

  # APP_URL=http://localhost:&amp;lt;--!host-port-number!--&amp;gt;
  # PROJECT_PORT=&amp;lt;--!common-port-number!--&amp;gt; OR 
  # PROJECT_PORT=[&amp;lt;--!host-port-number!--&amp;gt;,&amp;lt;--!internal-project-port-number!--&amp;gt;]
  PROJECT_PORT=[443,8360]
  # In case USE_COMMERCIAL_SSL is 'false', the Runner generates self-signed SSL certificates. However, you should set any name for ``COMMERCIAL_SSL_NAME``.
  # In case it is 'true', locate your commercial SSLs in the folder docker-blue-green-runner/.docker/ssl. See the comments in the .env above.
  USE_COMMERCIAL_SSL=true
  # Your domain name is recommended. The files 'your-app.com.key', 'your-app.com.crt', and 'your-app.com.chained.crt' should be in place.
  COMMERCIAL_SSL_NAME=your-app.com

  DOCKER_LAYER_CORRUPTION_RECOVERY=false

  NGINX_RESTART=false
  CONSUL_RESTART=false

  # The method of acquiring Docker images:
  # build (Used in developer's local environment or during Jenkins builds when a new image needs to be built, so this module is typically used)
  # registry (Used on deployment servers where images are fetched from a repository, so this module is used)
  # If you choose the "build" method, you don't need to input the values below since Dockerfile is used (no image is fetched from the Docker registry).
  GIT_IMAGE_LOAD_FROM=build
  GIT_IMAGE_LOAD_FROM_HOST=xxx
  GIT_IMAGE_LOAD_FROM_PATHNAME=xxx
  GIT_TOKEN_IMAGE_LOAD_FROM_USERNAME=xxx
  GIT_TOKEN_IMAGE_LOAD_FROM_PASSWORD=xxx
  GIT_IMAGE_VERSION=1.0.0

  PROJECT_NAME=your-app
  ## [IMPORTANT] Ensure it matches 'PROJECT_ROOT_IN_CONTAINER' below.
  PROJECT_LOCATION=/app
  PROJECT_PORT=[443,8360]
  # Example (8093,8094,11000...)
  ADDITIONAL_PORTS=

  CONSUL_KEY_VALUE_STORE=http://consul:8500/v1/kv/deploy/your-app

  # If you locate your project on ../ (upper folder)
  HOST_ROOT_LOCATION=/var/projects/your-app
  # If you locate your project's Dockerfile ../ (upper folder)
  DOCKER_FILE_LOCATION=/var/projects/your-app

  # This is for integrating health checkers such as "https://www.baeldung.com/spring-boot-actuators"
  # This path is used for both internal and external health checks.
  # Note: Do not include a leading slash ("/") at the start of the path.
  # Example: "api/v1/health" (correct), "/api/v1/health" (incorrect)
  APP_HEALTH_CHECK_PATH=api/v1/health

  # The BAD &amp;amp; GOOD conditions are checked using an "AND" condition.
  # To ignore the "BAD_APP_HEALTH_CHECK_PATTERN", set it to a non-existing value (e.g., "###lsdladc").
  BAD_APP_HEALTH_CHECK_PATTERN=DOWN

  # Pattern required for a successful health check.
  GOOD_APP_HEALTH_CHECK_PATTERN=UP

  # The following trick is just for skipping the check.
  # APP_HEALTH_CHECK_PATH=login
  # BAD_APP_HEALTH_CHECK_PATTERN=xxxxxxx
  # GOOD_APP_HEALTH_CHECK_PATTERN=Head


  # This is for environment variables for docker-compose-app-${app_env}.
  DOCKER_COMPOSE_ENVIRONMENT={"TZ":"Asia/Seoul"}
  # [IMPORTANT] You can pass any variable to Step 2 of your Dockerfile using DOCKER_BUILD_ARGS, e.g., DOCKER_BUILD_ARGS={"PROJECT_ROOT_IN_CONTAINER":"/app"}."
  DOCKER_BUILD_ARGS={"PROJECT_ROOT_IN_CONTAINER":"/app"}
  # For SSL, the host folder is recommended to be './.docker/ssl' to be synchronized with 'docker-compose-nginx-original.yml'.
  # [IMPORTANT] Run mkdir -p /var/projects/files/your-app/logs on your host machine
  DOCKER_COMPOSE_SELECTIVE_VOLUMES=["/var/projects/your-app/.docker/nginx/app.conf.ctmpl:/etc/nginx-template/app.conf.ctmpl","/var/projects/files/your-app/logs:/var/log/nginx"]
  # [IMPORTANT] Run mkdir -p /var/projects/files/nginx/logs on your host machine
  DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES=["/var/projects/files/nginx/logs:/var/log/nginx"]
  DOCKER_COMPOSE_HOST_VOLUME_CHECK=false

  NGINX_CLIENT_MAX_BODY_SIZE=50M

  USE_MY_OWN_APP_YML=false

  SKIP_BUILDING_APP_IMAGE=false

  # Docker-Swarm(stack) is currently a beta version. Use 'compose'.
  ORCHESTRATION_TYPE=compose

  ONLY_BUILDING_APP_IMAGE=false

  DOCKER_BUILD_MEMORY_USAGE=1G

  USE_NGINX_RESTRICTED_LOCATION=false
  # ex. /docs/api-app.html
  NGINX_RESTRICTED_LOCATION=xxx

  # If you set this to 'true', you won't need to configure SSL for your app. For instance, in a Spring Boot project, you won't have to create a ".jks" file. However, in rare situations, such as when it's crucial to secure all communication lines with SSL or when converting HTTPS to HTTP causes 'curl' errors, you might need to set it to 'false'.If you set this to 'true', you don't need to set SSL on your App like for example, for a Spring Boot project, you won't need to create the ".jks" file. However, in rare cases, such as ensuring all communication lines are SSL-protected, or when HTTPS to HTTP causes 'curl' errors, you might need to set it to 'false'.
  # 1) true : [Request]--&amp;gt; https (external network) --&amp;gt;Nginx--&amp;gt; http (internal network) --&amp;gt; App
  # 2) false :[Request]--&amp;gt; https (external network) --&amp;gt;Nginx--&amp;gt; httpS (internal network) --&amp;gt; App
  # !!! [IMPORTANT] As your App container below is Http, this should be set to 'true'.
  REDIRECT_HTTPS_TO_HTTP=true

  NGINX_LOGROTATE_FILE_NUMBER=7
  NGINX_LOGROTATE_FILE_SIZE=1M

  # You can change the values below. These settings for security related to ``set-safe-permissions.sh`` at the root of Docker-Blue-Green-Runner. 
  SHARED_VOLUME_GROUP_ID=1559
  SHARED_VOLUME_GROUP_NAME=mba-shared-volume-group
  UIDS_BELONGING_TO_SHARED_VOLUME_GROUP_ID=1000,1001

  USE_MY_OWN_NGINX_ORIGIN=false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2. Write your Dockerfile at the root of your project&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React sample (SPA)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ARG below is passed from Step 1. In SPA case, since Next.js isn’t being used, you may need to set up an additional Nginx server in your app. Spring Boot, Next.js, and NestJS each have their own servers, so no additional setup is needed for them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM node:18.20.4-slim AS build

ARG PROJECT_ROOT_IN_CONTAINER

RUN mkdir -p $PROJECT_ROOT_IN_CONTAINER
COPY .. $PROJECT_ROOT_IN_CONTAINER
WORKDIR $PROJECT_ROOT_IN_CONTAINER
RUN export NODE_OPTIONS="--max-old-space-size=2048"
RUN whereis npm &amp;amp;&amp;amp; alias npm='node --max_old_space_size=2048 /usr/local/bin/npm'
RUN export NODE_OPTIONS="--max-old-space-size=2048"
RUN if [ -d $PROJECT_ROOT_IN_CONTAINER/node_modules ]; then echo "[NOTICE] The node_modules folder exists. Skipping 'npm install'... "; else npm install --legacy-peer-deps; fi
RUN npm cache clean --force
RUN npm run build:prod

FROM nginx:stable

RUN apt-get update -qqy &amp;amp;&amp;amp; apt-get -qqy --force-yes install curl runit wget unzip vim &amp;amp;&amp;amp; \
    rm -rf /var/lib/apt/lists/* /var/cache/apt/*

ARG PROJECT_ROOT_IN_CONTAINER

COPY --chown=nginx --from=build $PROJECT_ROOT_IN_CONTAINER/dist/ $PROJECT_ROOT_IN_CONTAINER

USER root
WORKDIR $PROJECT_ROOT_IN_CONTAINER


COPY ./.docker/entrypoint.sh /entrypoint.sh
RUN chmod a+x /entrypoint.sh

ENTRYPOINT bash /entrypoint.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;entrypoint.sh &amp;amp; app.conf.ctmpl… these are not not necessary this is just my App’s setting.&lt;/p&gt;

&lt;p&gt;entrypoint.sh, app.conf.ctmpl.. these are just for my App settings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
cat /etc/nginx-template/app.conf.ctmpl &amp;gt; /etc/nginx/conf.d/default.conf
/usr/sbin/nginx -t &amp;amp;&amp;amp; exec /usr/sbin/nginx -g "daemon off;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
    listen 8360; # [IMPORTANT] PROJECT_PORT in .env Step1.
    server_name localhost;

    # Root directory
    root /app; 
    index index.html
    # Access and Error logs
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    # Gzip compression for performance improvement
    gzip on;
    gzip_comp_level 5;
    gzip_min_length 256;
    gzip_proxied any;
    gzip_vary on;

    gzip_types
        application/javascript
        application/json
        application/xml
        text/css
        text/plain;

    # Cache settings for static files for better performance
    location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }

    location / {
      try_files $uri $uri/ /index.html?$query_string;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Deploy daily with a simple git pull &amp;amp;&amp;amp; bash run.sh command&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For more details, please refer to &lt;a href="https://github.com/patternhelloworld/docker-blue-green-runner?tab=readme-ov-file#security" rel="noopener noreferrer"&gt;https://github.com/patternhelloworld/docker-blue-green-runner?tab=readme-ov-file#security&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for taking the time to read this!&lt;/p&gt;

</description>
      <category>sre</category>
      <category>docker</category>
      <category>laravel</category>
      <category>springboot</category>
    </item>
    <item>
      <title>Zero-Downtime Blue-Green Deployment with a Simple 'git pull &amp; bash run.sh' Command</title>
      <dc:creator>Andrew Kang-G</dc:creator>
      <pubDate>Tue, 05 Nov 2024 16:35:00 +0000</pubDate>
      <link>https://dev.to/andrewkangg/zero-downtime-blue-green-deployment-with-a-simple-git-pull-bash-runsh-command-2k82</link>
      <guid>https://dev.to/andrewkangg/zero-downtime-blue-green-deployment-with-a-simple-git-pull-bash-runsh-command-2k82</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/patternhelloworld/docker-blue-green-runner" rel="noopener noreferrer"&gt;https://github.com/patternhelloworld/docker-blue-green-runner&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No Unpredictable Errors in Reverse Proxy and Deployment&lt;/p&gt;

&lt;p&gt;If any error occurs in the app or router, deployment is halted to prevent any impact on the existing deployment&lt;/p&gt;

&lt;p&gt;For example, Traefik offers powerful dynamic configuration and service discovery; however, certain errors, such as a failure to detect containers (due to issues like unrecognized certificates), can lead to frustrating 404 errors that are hard to trace through logs alone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/76660749/traefik-404-page-not-found-when-use-https" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/76660749/traefik-404-page-not-found-when-use-https&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.traefik.io/t/getting-bad-gateway-404-page-when-supposed-to-route-to-container-port-8443/20398" rel="noopener noreferrer"&gt;https://community.traefik.io/t/getting-bad-gateway-404-page-when-supposed-to-route-to-container-port-8443/20398&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Manipulates NGINX configuration files directly to ensure container accessibility. It also tests configuration files by launching a test NGINX Docker instance, and if an NGINX config update via Consul-Template fails, Contingency Plan provided is activated to ensure connectivity to your containers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From Scratch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docker-Blue-Green-Runner's run.sh script is designed to simplify deployment: "With your .env, project, and a single Dockerfile, simply run 'bash run.sh'." This script covers the entire process from Dockerfile build to server deployment from scratch.&lt;br&gt;
In contrast, Traefik requires the creation and gradual adjustment of various configuration files, which can introduce the types of errors mentioned above.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Zero-Downtime Blue-Green Deployment with a Simple 'git pull &amp; bash run.sh' Command</title>
      <dc:creator>Andrew Kang-G</dc:creator>
      <pubDate>Mon, 04 Nov 2024 14:33:16 +0000</pubDate>
      <link>https://dev.to/andrewkangg/zero-downtime-blue-green-deployment-with-a-simple-git-pull-bash-runsh-command-4lcl</link>
      <guid>https://dev.to/andrewkangg/zero-downtime-blue-green-deployment-with-a-simple-git-pull-bash-runsh-command-4lcl</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/patternhelloworld/docker-blue-green-runner" rel="noopener noreferrer"&gt;https://github.com/patternhelloworld/docker-blue-green-runner&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why would I use this over Traefik?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Unpredictable Errors in Reverse Proxy&lt;br&gt;
Traefik offers powerful dynamic configuration and service discovery; however, certain errors, such as a failure to detect containers (due to issues like unrecognized certificates), can lead to frustrating 404 errors that are hard to trace through logs alone.&lt;br&gt;
&lt;a href="https://stackoverflow.com/questions/76660749/traefik-404-page-not-found-when-use-https" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/76660749/traefik-404-page-not-found-when-use-https&lt;/a&gt;&lt;br&gt;
&lt;a href="https://community.traefik.io/t/getting-bad-gateway-404-page-when-supposed-to-route-to-container-port-8443/20398" rel="noopener noreferrer"&gt;https://community.traefik.io/t/getting-bad-gateway-404-page-when-supposed-to-route-to-container-port-8443/20398&lt;/a&gt;&lt;br&gt;
Docker-Blue-Green-Runner manipulates NGINX configuration files directly to ensure container accessibility. It also tests configuration files by launching a test NGINX Docker instance, and if an NGINX config update via Consul-Template fails, Contingency Plan provided is activated to ensure connectivity to your containers.&lt;br&gt;
Additionally, the code is written in shell script, making it easier to trace the exact code section where an error occurs (as opposed to working with a binary).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;From Scratch&lt;br&gt;
Docker-Blue-Green-Runner's run.sh script is designed to simplify deployment: "With your .env, project, and a single Dockerfile, simply run 'bash run.sh'." This script covers the entire process from Dockerfile build to server deployment from scratch.&lt;br&gt;
In contrast, Traefik requires the creation and gradual adjustment of various configuration files, which can introduce the types of errors mentioned above.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Speed&lt;br&gt;
NGINX generally offers faster performance, though I acknowledge that Traefik's robust features can offset this advantage.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cicd</category>
      <category>sre</category>
      <category>webdev</category>
      <category>docker</category>
    </item>
  </channel>
</rss>
