<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Eryan Fauzan</title>
    <description>The latest articles on Forem by Eryan Fauzan (@eryanfauzan).</description>
    <link>https://forem.com/eryanfauzan</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%2F3862207%2Ffaef4495-bc10-4a5d-ad5d-3ae60b679e9f.jpeg</url>
      <title>Forem: Eryan Fauzan</title>
      <link>https://forem.com/eryanfauzan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/eryanfauzan"/>
    <language>en</language>
    <item>
      <title>Fix ERR_TOO_MANY_REDIRECTS: WordPress di Belakang AWS ALB</title>
      <dc:creator>Eryan Fauzan</dc:creator>
      <pubDate>Tue, 14 Apr 2026 03:03:19 +0000</pubDate>
      <link>https://forem.com/eryanfauzan/fix-errtoomanyredirects-wordpress-di-belakang-aws-alb-1114</link>
      <guid>https://forem.com/eryanfauzan/fix-errtoomanyredirects-wordpress-di-belakang-aws-alb-1114</guid>
      <description>&lt;p&gt;Kalau kamu migrasi WordPress yang tadinya pakai Nginx sebagai SSL terminator ke setup dengan AWS Application Load Balancer (ALB) di depannya, ada satu masalah klasik yang hampir pasti muncul: &lt;strong&gt;ERR_TOO_MANY_REDIRECTS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Artikel ini dokumentasi troubleshooting langsung dari production, mulai dari health check gagal, redirect loop, sampai fix di level Nginx dan WordPress.&lt;/p&gt;




&lt;h2&gt;
  
  
  Arsitektur Sebelum dan Sesudah
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Sebelumnya:&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;Client → HTTPS → Nginx (SSL termination) → WordPress HTTP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sesudah pakai ALB:&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;Client → HTTPS → ALB (SSL termination) → HTTP 80 → Nginx → WordPress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Di setup baru, &lt;strong&gt;ALB yang handle SSL&lt;/strong&gt;. Nginx dan WordPress tidak perlu tahu soal SSL, mereka cukup terima traffic HTTP biasa dari ALB.&lt;/p&gt;




&lt;h2&gt;
  
  
  Masalah 1: Health Check Gagal dengan Kode 301
&lt;/h2&gt;

&lt;p&gt;Setelah EC2 didaftarkan ke target group ALB, health check langsung &lt;strong&gt;Unhealthy&lt;/strong&gt; dengan pesan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Health checks failed with these codes: [301]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Kenapa Terjadi?
&lt;/h3&gt;

&lt;p&gt;ALB secara default expect response &lt;code&gt;200&lt;/code&gt; dari health check path. Tapi WordPress melakukan redirect (301), misalnya ke &lt;code&gt;/en/&lt;/code&gt; atau ke HTTPS, sehingga dianggap gagal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solusinya
&lt;/h3&gt;

&lt;p&gt;Pergi ke &lt;strong&gt;EC2 &amp;gt; Target Groups &amp;gt; Health checks&lt;/strong&gt;, lalu edit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Health check path &lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
&lt;span class="na"&gt;Success codes     &lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;200,301&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Atau ganti path ke endpoint yang langsung return 200. Tidak perlu menyentuh WordPress sama sekali.&lt;/p&gt;




&lt;h2&gt;
  
  
  Masalah 2: ERR_TOO_MANY_REDIRECTS
&lt;/h2&gt;

&lt;p&gt;Setelah domain di-point ke ALB, browser langsung menampilkan &lt;strong&gt;ERR_TOO_MANY_REDIRECTS&lt;/strong&gt;. Ini terjadi karena redirect loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALB kirim HTTP ke Nginx
→ Nginx redirect ke HTTPS
→ ALB nerima HTTPS, forward lagi ke Nginx sebagai HTTP
→ Loop tidak berhenti
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Fix Bagian 1: Bersihkan Config Nginx
&lt;/h2&gt;

&lt;p&gt;Cek di mana redirect HTTPS berada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo grep&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; &lt;span class="s2"&gt;"return 301"&lt;/span&gt; /etc/nginx/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;/etc/nginx/sites-available/wordpress:5:&lt;/span&gt;    &lt;span class="s"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="nv"&gt;$host$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Karena ALB sudah handle SSL termination, Nginx tidak perlu lagi melakukan redirect HTTPS. Backup dulu, lalu edit config-nya:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo cp&lt;/span&gt; /etc/nginx/sites-available/wordpress /etc/nginx/sites-available/wordpress.bak
&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/nginx/sites-available/wordpress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ganti seluruh isi config dengan versi yang hanya listen di port 80 tanpa redirect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt; &lt;span class="s"&gt;www.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;client_max_body_size&lt;/span&gt; &lt;span class="mi"&gt;50M&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/var/www/html/wordpress&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt; &lt;span class="s"&gt;index.htm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Frame-Options&lt;/span&gt; &lt;span class="s"&gt;DENY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Content-Type-Options&lt;/span&gt; &lt;span class="s"&gt;nosniff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="n"&gt;/index.php?&lt;/span&gt;&lt;span class="nv"&gt;$args&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;\.php$&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="nc"&gt;snippets/fastcgi-php&lt;/span&gt;&lt;span class="s"&gt;.conf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_pass&lt;/span&gt; &lt;span class="s"&gt;unix:/var/run/php/php7.4-fpm.sock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;SCRIPT_FILENAME&lt;/span&gt; &lt;span class="nv"&gt;$document_root$fastcgi_script_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;HTTPS&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="s"&gt;fastcgi_params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Poin penting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hapus server block port 443&lt;/strong&gt; karena SSL sekarang di ALB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hapus &lt;code&gt;return 301 https://...&lt;/code&gt;&lt;/strong&gt; karena ini biang kerok redirect loop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tambah &lt;code&gt;fastcgi_param HTTPS on&lt;/code&gt;&lt;/strong&gt; biar PHP/WordPress tahu request-nya HTTPS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test dan reload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Fix Bagian 2: wp-config.php
&lt;/h2&gt;

&lt;p&gt;Setelah Nginx beres, masih bisa terjadi redirect loop dari sisi WordPress sendiri. Ini karena WordPress melihat request datang via HTTP (dari ALB ke Nginx), tapi WordPress dikonfigurasi untuk berjalan di HTTPS.&lt;/p&gt;

&lt;p&gt;WordPress butuh dikasih tahu bahwa request originalnya adalah HTTPS melalui header &lt;code&gt;X-Forwarded-Proto&lt;/code&gt; yang dikirim ALB.&lt;/p&gt;

&lt;p&gt;Buka &lt;code&gt;wp-config.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /var/www/html/wordpress/wp-config.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tambahkan snippet ini &lt;strong&gt;sebelum&lt;/strong&gt; baris &lt;code&gt;require_once ABSPATH . 'wp-settings.php';&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Fix HTTPS detection behind ALB/reverse proxy */&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'HTTP_X_FORWARDED_PROTO'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'HTTP_X_FORWARDED_PROTO'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'https'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'HTTPS'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'on'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setelah save, refresh browser dan redirect loop selesai.&lt;/p&gt;




&lt;h2&gt;
  
  
  Kenapa Dua Fix Diperlukan?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Masalah&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Nginx&lt;/td&gt;
&lt;td&gt;Redirect HTTP ke HTTPS meski ALB sudah handle SSL&lt;/td&gt;
&lt;td&gt;Hapus &lt;code&gt;return 301&lt;/code&gt;, listen HTTP only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WordPress&lt;/td&gt;
&lt;td&gt;Tidak tahu request original adalah HTTPS&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;$_SERVER['HTTPS']&lt;/code&gt; dari header &lt;code&gt;X-Forwarded-Proto&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Keduanya saling terkait. Nginx fix menghentikan redirect di level server, tapi WordPress masih bisa generate URL HTTP dan redirect sendiri kalau tidak tahu konteks HTTPS-nya.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Masalah ini sebenarnya bisa dicegah jauh lebih cepat kalau dari awal sudah tahu detail setup server yang dipasang vendor.&lt;/p&gt;

&lt;p&gt;Dalam kasus ini, vendor sebelumnya menginstall Nginx sebagai SSL terminator langsung di EC2. Tidak ada dokumentasi yang diserahkan, tidak ada catatan soal config apa yang aktif. Ketika infrastruktur berubah (masuk ALB), asumsinya server tinggal disambungkan. Ternyata ada konfigurasi lama yang konflik.&lt;/p&gt;

&lt;p&gt;Beberapa hal yang sebaiknya didokumentasikan atau ditanyakan ke vendor sebelum migrasi:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Soal web server:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nginx atau Apache? Versi berapa?&lt;/li&gt;
&lt;li&gt;Ada berapa config file aktif di &lt;code&gt;sites-enabled&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Apakah ada redirect HTTPS di level Nginx?&lt;/li&gt;
&lt;li&gt;SSL terminate di mana, Nginx langsung atau ada layer lain?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Soal WordPress:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ada custom snippet di &lt;code&gt;wp-config.php&lt;/code&gt; selain yang default?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;siteurl&lt;/code&gt; dan &lt;code&gt;home&lt;/code&gt; di database point ke HTTP atau HTTPS?&lt;/li&gt;
&lt;li&gt;Ada plugin caching atau security yang bisa interfere dengan redirect?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Soal server secara umum:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Service apa saja yang berjalan (&lt;code&gt;systemctl list-units --type=service&lt;/code&gt;)?&lt;/li&gt;
&lt;li&gt;PHP versi berapa, dan pakai FPM socket atau TCP?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Minta handover document atau minimal akses SSH sebelum mulai integrasi. Troubleshooting config orang lain tanpa dokumentasi itu bisa makan waktu jauh lebih lama dari yang seharusnya.&lt;/p&gt;




&lt;h2&gt;
  
  
  Catatan Tambahan
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Kalau ada dua config file untuk domain yang sama (misalnya &lt;code&gt;mysite&lt;/code&gt; dan &lt;code&gt;mysite-old.conf&lt;/code&gt;), cek symlink di &lt;code&gt;/etc/nginx/sites-enabled/&lt;/code&gt; dan pastikan hanya satu yang aktif&lt;/li&gt;
&lt;li&gt;Header &lt;code&gt;X-Forwarded-Proto&lt;/code&gt; dikirim otomatis oleh ALB, tidak perlu konfigurasi tambahan di sisi ALB&lt;/li&gt;
&lt;li&gt;Setelah semua beres, update juga &lt;strong&gt;ALB Listener Rules&lt;/strong&gt; agar traffic untuk domain tersebut di-forward ke target group yang benar&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Tested on: Ubuntu 22.04, Nginx 1.24, WordPress 6.x, AWS ALB&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>wordpress</category>
      <category>beginners</category>
      <category>indonesia</category>
    </item>
    <item>
      <title>ALB Deep Dive: HTTPS, Host Header Routing, dan CNAME tanpa Route53</title>
      <dc:creator>Eryan Fauzan</dc:creator>
      <pubDate>Fri, 10 Apr 2026 09:40:39 +0000</pubDate>
      <link>https://forem.com/eryanfauzan/alb-deep-dive-https-host-header-routing-dan-cname-tanpa-route53-4pn6</link>
      <guid>https://forem.com/eryanfauzan/alb-deep-dive-https-host-header-routing-dan-cname-tanpa-route53-4pn6</guid>
      <description>&lt;p&gt;Kalau lo udah ngikutin artikel pertama, sekarang servicenya udah jalan di belakang ALB. Tapi setupnya masih basic masih HTTP dan baru satu service.&lt;/p&gt;

&lt;p&gt;Di artikel ini gua bahas setup ALB yang lebih nyata dari pengalaman di production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTPS dengan SSL (Wildcard) yang dibeli sendiri, bukan dari ACM gratis&lt;/li&gt;
&lt;li&gt;Satu ALB untuk banyak service sekaligus&lt;/li&gt;
&lt;li&gt;Routing berdasarkan subdomain via host header&lt;/li&gt;
&lt;li&gt;CNAME di DNS provider lo&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Kenapa Satu ALB untuk Banyak Service?
&lt;/h2&gt;

&lt;p&gt;ALB itu billingnya per jam, bukan per service. Jadi kalau lo punya 5 service dan masing-masing pake ALB sendiri, lo bayar bakal mahal banget itu wkwk.&lt;/p&gt;

&lt;p&gt;Pake satu ALB, lo bisa handle banyak service sekaligus lewat listener rules. &lt;code&gt;app1.domain.com&lt;/code&gt; ke service A, &lt;code&gt;app2.domain.com&lt;/code&gt; ke service B, semua lewat satu ALB yang sama.&lt;/p&gt;

&lt;p&gt;Ini yang gua pake di production dan ini yang paling worth buat setup dari awal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Beli SSL dan Import ke ACM
&lt;/h2&gt;

&lt;p&gt;Kebanyakan artikel yang ada pake SSL gratis dari ACM langsung. Tapi kalau lo beli domain di IDCloudhost, NiagaHoster, RumahWeb atau provider lokal lain dan mau pake SSL yang lo beli sendiri, caranya beda.&lt;/p&gt;

&lt;h3&gt;
  
  
  Format yang Dibutuhin ACM
&lt;/h3&gt;

&lt;p&gt;Waktu import SSL ke ACM, lo butuh tiga hal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Certificate body:&lt;/strong&gt; file &lt;code&gt;.crt&lt;/code&gt; atau &lt;code&gt;.pem&lt;/code&gt; dari SSL lo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private key:&lt;/strong&gt; file &lt;code&gt;.key&lt;/code&gt; yang lo generate waktu request SSL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Certificate chain:&lt;/strong&gt; file intermediate/bundle dari provider SSL lo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kalau lo beli SSL di third party, biasanya lo dapat file zip yang isinya ketiga file itu. Ada yang harus diminta lewat open ticket ada yang biasanya udah include langsung di service SSL, pokoknya tergantung dari third party lo. Pastiin lo nyimpen private keynya waktu generate CSR karena ACM bakal minta itu waktu import.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cara Import ke ACM
&lt;/h3&gt;

&lt;p&gt;Masuk ke AWS Console &amp;gt; Certificate Manager &amp;gt; Import certificate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Paste isi file certificate body ke kolom &lt;strong&gt;Certificate body&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Paste isi private key ke kolom &lt;strong&gt;Certificate private key&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Paste isi certificate chain ke kolom &lt;strong&gt;Certificate chain&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Klik &lt;strong&gt;Next&lt;/strong&gt;, kasih tag kalau mau, lalu &lt;strong&gt;Import&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Setelah berhasil, status certificate lo bakal &lt;strong&gt;Issued&lt;/strong&gt;. Kalau statusnya &lt;strong&gt;Pending validation&lt;/strong&gt;, itu berarti lo pake request ACM bukan import, pastiin lo pilih yang import.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Catatan:&lt;/strong&gt; Private key itu sensitif. Jangan pernah commit ke repository atau share ke siapapun.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 2: Buat Target Group per Service
&lt;/h2&gt;

&lt;p&gt;Sebelum setup ALB, lo harus siapin target group dulu untuk setiap service yang mau lo expose.&lt;/p&gt;

&lt;p&gt;Masuk ke EC2 &amp;gt; Target Groups &amp;gt; Create target group:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Target type:&lt;/strong&gt; IP (wajib untuk ECS Fargate karena networkMode awsvpc)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protocol:&lt;/strong&gt; HTTP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port:&lt;/strong&gt; sesuaikan dengan port container lo, misalnya &lt;code&gt;3000&lt;/code&gt; untuk service A, &lt;code&gt;8000&lt;/code&gt; untuk service B&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPC:&lt;/strong&gt; pilih VPC yang sama dengan ECS service lo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health check protocol:&lt;/strong&gt; HTTP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health check path:&lt;/strong&gt; endpoint health check lo, misalnya &lt;code&gt;/api/v1/health&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ulangi untuk setiap service yang mau lo attach ke ALB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Soal Health Check Path
&lt;/h3&gt;

&lt;p&gt;Ini yang sering bikin orang nyangkut. Health check path harus return status &lt;code&gt;200&lt;/code&gt;. Kalau lo belum punya endpoint health check di NestJS lo, bikin dulu yang simpel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;health&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kalau health check gagal, ALB bakal terus drain dan replace task lo sampe circuit breaker dari ECS eksekusi biasanya sampe 3x cuma ini makan waktu sampe 20 menitan.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Buat ALB dan Setup Listener 443
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Buat ALB
&lt;/h3&gt;

&lt;p&gt;Masuk ke EC2 &amp;gt; Load Balancers &amp;gt; Create &amp;gt; Application Load Balancer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name:&lt;/strong&gt; kasih nama yang deskriptif&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheme:&lt;/strong&gt; Internet-facing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IP address type:&lt;/strong&gt; IPv4&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPC:&lt;/strong&gt; pilih VPC yang sama dengan ECS lo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subnets:&lt;/strong&gt; pilih minimal 2 public subnet di AZ yang berbeda, ini wajib&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security group:&lt;/strong&gt; buat security group baru yang allow inbound port 443 dari &lt;code&gt;0.0.0.0/0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Buat Listener 443
&lt;/h3&gt;

&lt;p&gt;Di bagian Listeners waktu buat ALB:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Protocol:&lt;/strong&gt; HTTPS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port:&lt;/strong&gt; 443&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default SSL/TLS certificate:&lt;/strong&gt; pilih certificate yang baru lo import di Step 1&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default action:&lt;/strong&gt; untuk sekarang pilih "Return fixed response" dengan status 404, nanti lo ganti waktu tambah rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Klik &lt;strong&gt;Create load balancer&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Setup Listener Rules by Host Header
&lt;/h2&gt;

&lt;p&gt;Ini inti dari artikel ini. Listener rules yang nentuin subdomain mana routing ke service mana.&lt;/p&gt;

&lt;p&gt;Masuk ke EC2 &amp;gt; Load Balancers &amp;gt; pilih ALB lo &amp;gt; tab &lt;strong&gt;Listeners&lt;/strong&gt; &amp;gt; klik &lt;strong&gt;View/edit rules&lt;/strong&gt; di listener 443.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tambah Rule per Service
&lt;/h3&gt;

&lt;p&gt;Klik &lt;strong&gt;Add rule&lt;/strong&gt; untuk setiap service:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule untuk app1.domain.com:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Condition:&lt;/strong&gt; Host header = &lt;code&gt;app1.domain.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action:&lt;/strong&gt; Forward to target group &lt;code&gt;app1-target-group&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Priority:&lt;/strong&gt; 1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Rule untuk app2.domain.com:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Condition:&lt;/strong&gt; Host header = &lt;code&gt;app2.domain.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action:&lt;/strong&gt; Forward to target group &lt;code&gt;app2-target-group&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Priority:&lt;/strong&gt; 2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ulangi untuk setiap service yang lo punya.&lt;/p&gt;

&lt;h3&gt;
  
  
  Default Rule
&lt;/h3&gt;

&lt;p&gt;Default rule itu yang jalan kalau request masuk tapi ga match sama rule manapun. Jangan dibiarkan kosong atau error, kasih fixed response 404 atau redirect ke halaman utama lo (kalo gua biasanya halaman landing page).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Penting:&lt;/strong&gt; Urutan priority itu ngaruh. ALB ngecheck rules dari priority terkecil ke terbesar. Kalau ada dua rules yang bisa match, yang prioritynya lebih kecil yang menang.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 5: Tambah CNAME di DNS Provider Lo
&lt;/h2&gt;

&lt;p&gt;Ini yang beda dari semua artikel lain yang pake Route53. Lo tinggal point subdomain lo ke ALB DNS name, dan ini bisa lo lakuin di DNS provider manapun yang lo pake, Cloudflare, Domainesia, Niagahoster, atau yang lain. Caranya pada dasarnya sama.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ambil ALB DNS Name
&lt;/h3&gt;

&lt;p&gt;Masuk ke EC2 &amp;gt; Load Balancers &amp;gt; pilih ALB lo. Di bagian bawah lo bakal liat &lt;strong&gt;DNS name&lt;/strong&gt;, formatnya kira-kira:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nama-alb-lo-1234567890.ap-southeast-1.elb.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tambah CNAME Record
&lt;/h3&gt;

&lt;p&gt;Di DNS provider lo, tambah record baru:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type:&lt;/strong&gt; CNAME&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name/Host:&lt;/strong&gt; &lt;code&gt;app1&lt;/code&gt; (subdomain lo, fullnya jadi &lt;code&gt;app1.domain.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Value/Target:&lt;/strong&gt; paste ALB DNS name lo di sini&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TTL:&lt;/strong&gt; 300 (5 menit cukup buat testing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ulangi untuk setiap subdomain yang lo punya.&lt;/p&gt;

&lt;p&gt;Tunggu beberapa menit untuk DNS propagation, biasanya 5 menit kalau TTL lo 300.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Attach Target Group ke ECS Service
&lt;/h2&gt;

&lt;p&gt;Kalau ECS service lo belum di-attach ke target group yang baru lo buat, lo perlu update servicenya.&lt;/p&gt;

&lt;p&gt;Masuk ke ECS &amp;gt; cluster lo &amp;gt; service &amp;gt; &lt;strong&gt;Update service&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Di bagian &lt;strong&gt;Load balancing&lt;/strong&gt;, tambah target group yang sesuai&lt;/li&gt;
&lt;li&gt;Pastiin container name dan port match sama yang ada di task definition lo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Setelah update, ECS bakal register task yang jalan ke target group tersebut.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Verifikasi
&lt;/h2&gt;

&lt;p&gt;Setelah DNS propagation selesai, cek satu-satu:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Target Group&lt;/strong&gt; di console, pastiin health check statusnya &lt;code&gt;healthy&lt;/code&gt;. Kalau masih &lt;code&gt;initial&lt;/code&gt; tunggu beberapa menit, kalau &lt;code&gt;unhealthy&lt;/code&gt; cek health check path lo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Akses via browser&lt;/strong&gt;, buka &lt;code&gt;https://app1.domain.com&lt;/code&gt; dan pastiin HTTPSnya valid dan app lo respond&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cek certificate&lt;/strong&gt;, klik ikon gembok di browser, pastiin certificate yang kepakai itu yang lo import tadi&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Kalau HTTPSnya invalid atau browser bilang "certificate not trusted", kemungkinan certificate chain lo waktu import belum lengkap.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Beberapa hal yang gua pelajarin dari setup ini di production:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Urutan listener rules itu ngaruh&lt;/strong&gt;&lt;br&gt;
ALB ngecheck dari priority terkecil. Kalau lo punya wildcard rule kayak &lt;code&gt;*.domain.com&lt;/code&gt;, taruh di priority paling besar (terakhir) supaya ga nutup rules yang lebih spesifik.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Default rule jangan dikosongkan&lt;/strong&gt;&lt;br&gt;
Kalau ada request yang masuk ke ALB tapi ga match rule manapun, default rule yang handle. Kasih fixed response 404 daripada biarkan error ga jelas atau langsung ke landing page aja.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Health check path harus konsisten&lt;/strong&gt;&lt;br&gt;
Gua pernah ganti path health check di code tapi lupa update di target group. Hasilnya task terus-terusan di-replace sama ECS karena ALB nganggep servicenya unhealthy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Certificate chain harus lengkap&lt;/strong&gt;&lt;br&gt;
Waktu import SSL ke ACM, certificate chain itu wajib. Kalau lo skip atau paste yang salah, browser bakal bilang certificate not trusted meskipun SSL lo valid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. TTL DNS waktu testing&lt;/strong&gt;&lt;br&gt;
Set TTL ke 300 dulu waktu testing. Kalau lo set terlalu tinggi dan ada yang salah, lo harus nunggu lama banget sampai DNS update.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Satu service satu target group, jangan dicampur&lt;/strong&gt;&lt;br&gt;
Gua pernah kena kasus aneh, refresh page di &lt;code&gt;app1.domain.com&lt;/code&gt; tapi responsenya bergantian dari dua service yang berbeda. Ternyata penyebabnya dua ECS service lo attach ke target group yang sama. ALB by default distribusi traffic secara round robin ke semua task yang registered di target group tersebut. Jadi request pertama ke service A, request kedua ke service B, terus bergantian. Solusinya simpel: satu service harus punya satu target group tersendiri.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Satu ALB bisa handle banyak certificate&lt;/strong&gt;&lt;br&gt;
Kalau lo punya beberapa domain yang berbeda (bukan subdomain), ALB support multiple certificate via SNI. Lo bisa import beberapa SSL dan attach ke listener yang sama.&lt;/p&gt;




&lt;h2&gt;
  
  
  Penutup
&lt;/h2&gt;

&lt;p&gt;Setelah setup ini selesai, lo punya satu ALB yang handle semua service lo dengan HTTPS yang proper. Tiap kali mau tambah service baru, tinggal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Buat target group baru&lt;/li&gt;
&lt;li&gt;Tambah listener rule baru di ALB&lt;/li&gt;
&lt;li&gt;Tambah CNAME baru di DNS provider lo&lt;/li&gt;
&lt;li&gt;Attach target group ke ECS service&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ga perlu buat ALB baru, ga perlu beli SSL baru selama domainnya masih sama.&lt;/p&gt;

&lt;p&gt;Kalau ada pertanyaan drop di komentar, gua jawab sesuai pengalaman gua.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>backend</category>
      <category>webdev</category>
      <category>indonesia</category>
    </item>
    <item>
      <title>Deploy NestJS ke AWS ECS Fargate Secara Manual: Cara Paling Simpel Buat Mulai</title>
      <dc:creator>Eryan Fauzan</dc:creator>
      <pubDate>Sun, 05 Apr 2026 13:56:18 +0000</pubDate>
      <link>https://forem.com/eryanfauzan/deploy-nestjs-ke-aws-ecs-fargate-secara-manual-cara-paling-simpel-buat-mulai-10am</link>
      <guid>https://forem.com/eryanfauzan/deploy-nestjs-ke-aws-ecs-fargate-secara-manual-cara-paling-simpel-buat-mulai-10am</guid>
      <description>&lt;p&gt;Ini artikel pertama gua di dev.to. Gua nulis ini karena makanan sehari-hari deploy NestJS ke ECS Fargate dan sebagai catetan gua juga di kala lupa.&lt;/p&gt;

&lt;p&gt;Gua bakal nulis dari pengalaman nyata setup yang gua pake di kerjaan, bukan setup ideal tapi work in production karena waktu dan tenaga yang terbatas.&lt;/p&gt;

&lt;p&gt;konten bahasa Indonesia yang ngebahas ini step by step jarang kayaknya ya wkwk atau terlalu niche ya? ajak-ajak gua lah kalo ada pembahasan terkait AWS khususnya DevOps dan WebDev. &lt;/p&gt;

&lt;p&gt;buat step by step gua bikin masih narrative tapi gua coba bahas sedetail mungkin, gambarnya bertahap gua lengkapin &lt;/p&gt;

&lt;p&gt;kemungkinan nantinya tulisan ini bakal ngelink ke beberapa tutorial lainnya dan aka di update bertahap.&lt;br&gt;
Semoga artikel ini ngebantu lo yang lagi di posisi yang sama.&lt;/p&gt;


&lt;h2&gt;
  
  
  Kenapa ECS Fargate, Bukan EC2?
&lt;/h2&gt;

&lt;p&gt;Kalau lo baru mau mulai deploy NestJS app ke AWS, pilihan pertama yang sering bikin bingung adalah: pakai EC2 atau ECS?&lt;/p&gt;

&lt;p&gt;EC2 itu simpel secara konsep, lo dapat satu virtual machine (temen gua beberapa ada yang kecele dia rada binggung saat gua bilang VM, gua bilang kayak semacam VPS baru nyambung), lo install Node.js, lo jalanin app lo. Tapi di balik itu lo harus urus sendiri: update OS, manage process dengan PM2, handle restart kalau app crash, dan kalau mau scale lo harus setup sendiri.&lt;/p&gt;

&lt;p&gt;ECS Fargate beda. Lo ga pegang server sama sekali. Lo cukup kasih container, define berapa CPU dan memory yang dibutuhkan, dan AWS yang urus sisanya kayak scheduling, restart, bahkan scaling kalau lo mau. Lo bayar per detik container jalan, bukan per jam instance hidup.&lt;/p&gt;

&lt;p&gt;Artikel ini bakal ngajarin lo cara deploy NestJS app ke ECS Fargate secara manual tanpa CI/CD dulu. Cukup dari local, push ke ECR, setup di console AWS, dan app lo jalan di belakang ALB.&lt;/p&gt;

&lt;p&gt;Ini cara yang paling gampang buat mulai, dan kalau lo udah paham flow-nya, nambah CI/CD belakangan jadi jauh lebih gampang.&lt;/p&gt;


&lt;h2&gt;
  
  
  Prasyarat
&lt;/h2&gt;

&lt;p&gt;Sebelum mulai, pastiin lo udah punya hal-hal berikut:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS Side:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS account yang udah aktif&lt;/li&gt;
&lt;li&gt;IAM user dengan akses ke ECR dan ECS &lt;strong&gt;(jangan pakai root account)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;ECR repository yang udah dibuat&lt;/li&gt;
&lt;li&gt;RDS PostgreSQL instance yang udah jalan di VPC yang sama&lt;/li&gt;
&lt;li&gt;ALB yang udah di-setup atau siap di-setup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Local Side:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS CLI v2 terinstall dan sudah di-configure (&lt;code&gt;aws configure&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Docker Desktop terinstall dan jalan&lt;/li&gt;
&lt;li&gt;NestJS app yang udah siap di-deploy&lt;/li&gt;
&lt;li&gt;Git (karena script deploy kita bakal pakai branch name dan commit hash buat image tag)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;VPC dengan minimal 2 public subnet untuk ALB&lt;/li&gt;
&lt;li&gt;Security group yang udah direncanain ECS task perlu bisa terima traffic dari ALB, dan bisa konek ke RDS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kalau semua udah siap, kita mulai dari langkah pertama.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 1: Dockerize NestJS App
&lt;/h2&gt;

&lt;p&gt;Buat &lt;code&gt;Dockerfile&lt;/code&gt; di root project lo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ---------- builder stage ----------&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:22-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/app&lt;/span&gt;

&lt;span class="c"&gt;# install build deps (include dev deps because we need tsc)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="c"&gt;# copy source &amp;amp; build&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# ---------- production stage ----------&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:22-alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/app&lt;/span&gt;

&lt;span class="c"&gt;# copy only production deps and app metadata&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="c"&gt;# install only production deps&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--omit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev

&lt;span class="c"&gt;# copy built artifacts from builder&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/app/dist ./dist&lt;/span&gt;

&lt;span class="c"&gt;# create non-root user (optional but recommended)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-S&lt;/span&gt; app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="nt"&gt;-S&lt;/span&gt; app &lt;span class="nt"&gt;-G&lt;/span&gt; app

&lt;span class="c"&gt;# create exports directory with proper permissions&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /usr/app/exports &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; app:app /usr/app

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; app&lt;/span&gt;

&lt;span class="c"&gt;# GANTI INI -- sesuaikan dengan port NestJS app lo&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/main.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beberapa hal yang perlu diperhatiin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pakai &lt;strong&gt;multi-stage build&lt;/strong&gt;. Stage pertama buat build, stage kedua buat production image. Hasilnya image lebih kecil karena ga bawa devDependencies.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;node:22-alpine&lt;/code&gt; versi LTS terbaru, lebih ringan dari image full.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm ci --omit=dev&lt;/code&gt; lebih modern dari &lt;code&gt;--only=production&lt;/code&gt;, install hanya production dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-root user&lt;/strong&gt; ini security best practice yang sering dilewatin. Container lo ga jalan sebagai root, jadi kalau ada exploit, dampaknya lebih terbatas.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mkdir exports&lt;/code&gt; kalau app lo ada fitur generate file, direktori ini udah siap dengan permission yang bener.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test dulu di local sebelum push ke ECR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; nama-app-lo &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 nama-app-lo &lt;span class="c"&gt;# GANTI INI - sesuaikan port&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: Setup ECR dan Push Image
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Buat ECR Repository
&lt;/h3&gt;

&lt;p&gt;Masuk ke AWS Console &amp;gt; ECR &amp;gt; Create repository. Kasih nama yang deskriptif, misalnya &lt;code&gt;nama-project/nama-service&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;kalo mau liat push commandnya tinggal klik repository yang udah dibuat terus klik disana ada button "View Push Command"&lt;/p&gt;

&lt;p&gt;tapi gua ada cara lebih gampang lagi biar ga berkali-kali push command kalo mau push image&lt;/p&gt;




&lt;h3&gt;
  
  
  Script Deploy ke ECR
&lt;/h3&gt;

&lt;p&gt;Ini script yang gua pake buat build dan push image dari local ke ECR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="c"&gt;# Pastiin AWS CLI ada di PATH lo&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/c/Program Files/Amazon/AWSCLIV2:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Using AWS CLI from: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;which aws.exe&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Variables&lt;/span&gt;
&lt;span class="nv"&gt;BRANCH_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--abbrev-ref&lt;/span&gt; HEAD&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;IMAGE_TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BRANCH_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--short&lt;/span&gt; HEAD&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;LAST_COMMIT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git log &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="nt"&gt;--pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;format:&lt;span class="s2"&gt;"%h - %s (%cr) &amp;lt;%an&amp;gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"ap-southeast-1"&lt;/span&gt; &lt;span class="c"&gt;# GANTI INI - sesuaikan region lo&lt;/span&gt;
&lt;span class="nv"&gt;ECR_URI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"YOUR_ACCOUNT_ID.dkr.ecr.&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.amazonaws.com/YOUR_REPO_NAME"&lt;/span&gt; &lt;span class="c"&gt;# GANTI INI&lt;/span&gt;

&lt;span class="c"&gt;# Display deployment info&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== Deployment Information ==="&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Branch: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BRANCH_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Commit: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LAST_COMMIT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Image Tag: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IMAGE_TAG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"================================"&lt;/span&gt;

&lt;span class="c"&gt;# Authenticate Docker ke ECR&lt;/span&gt;
aws.exe ecr get-login-password &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | docker login &lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ECR_URI&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Build &amp;amp; tag&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; nama-app-lo &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="c"&gt;# GANTI INI&lt;/span&gt;
docker tag nama-app-lo:latest &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ECR_URI&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:latest"&lt;/span&gt; &lt;span class="c"&gt;# GANTI INI&lt;/span&gt;
docker tag nama-app-lo:latest &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ECR_URI&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IMAGE_TAG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="c"&gt;# GANTI INI&lt;/span&gt;

&lt;span class="c"&gt;# Push&lt;/span&gt;
docker push &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ECR_URI&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:latest"&lt;/span&gt;
docker push &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ECR_URI&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IMAGE_TAG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Deployed image tag: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IMAGE_TAG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kenapa gua push dua tag sekaligus?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;latest&lt;/code&gt; yang direferensiin di ECS task definition, jadi tiap deploy ECS tinggal pull yang terbaru&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;branch-commithash&lt;/code&gt; buat versioning, kalau ada masalah lo tau persis commit mana yang jalan di production&lt;/li&gt;
&lt;li&gt;ini kepake banget saat lo nantinya di minta buat pisahan enviroment misalnya development, staging dan production lo ga binggung enviroment mana pake image yang mana dan branch. karena real casenya dev, stag, prod itu beda-beda semua enviromentnya saat ada keterbatasan team ini bisa nolong lo dari kepusingan itu&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 3: Simpan .env di S3
&lt;/h2&gt;

&lt;p&gt;Ini bagian yang jarang dibahas tapi sangat praktis. Daripada hardcode environment variable satu-satu di task definition, lo bisa simpan &lt;code&gt;.env&lt;/code&gt; file di S3 dan ECS yang bakal load otomatis waktu container start.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kenapa S3?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Lebih gampang di-manage, tinggal update file di S3, ga perlu update task definition&lt;/li&gt;
&lt;li&gt;Lebih aman, file ada di S3 dengan permission yang bisa dikontrol&lt;/li&gt;
&lt;li&gt;Familiar, format &lt;code&gt;.env&lt;/code&gt; yang sama kayak di local&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cara Setup
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Buat S3 bucket khusus buat env files:&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;nama-bucket-lo/
  nama-app-lo/
    staging/
      .env
    production/
      .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Upload .env ke S3:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;cp&lt;/span&gt; .env s3://nama-bucket-lo/nama-app-lo/staging/.env &lt;span class="c"&gt;# GANTI INI&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Kasih permission ke ECS Task Execution Role:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ECS butuh permission buat baca file dari S3. Tambahkan policy ini ke &lt;code&gt;ecsTaskExecutionRole&lt;/code&gt; di IAM:&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;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&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;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::nama-bucket-lo/*"&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;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:GetBucketLocation"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::nama-bucket-lo"&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;h2&gt;
  
  
  Step 4: Setup Task Definition
&lt;/h2&gt;

&lt;p&gt;Task definition itu blueprint container lo di ECS. Define image mana yang dipakai, berapa resource yang dialokasiin, port mana yang dibuka, dan dari mana env-nya diload.&lt;/p&gt;

&lt;p&gt;Ini contoh task definition yang gua pake:&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;"family"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nama-service-lo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"containerDefinitions"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nama-service-lo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_ACCOUNT_ID.dkr.ecr.ap-southeast-1.amazonaws.com/YOUR_REPO:latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cpu"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"portMappings"&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;"containerPort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"hostPort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tcp"&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;"essential"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"environment"&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;"environmentFiles"&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;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::nama-bucket-lo/nama-app-lo/staging/.env"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3"&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;"logConfiguration"&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;"logDriver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"awslogs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"options"&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;"awslogs-group"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/ecs/nama-service-lo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"awslogs-region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ap-southeast-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"awslogs-stream-prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ecs"&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;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"executionRoleArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::YOUR_ACCOUNT_ID:role/ecsTaskExecutionRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"networkMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"awsvpc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requiresCompatibilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"FARGATE"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cpu"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"memory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"512"&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;Beberapa hal yang perlu diperhatiin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cpu: 256&lt;/code&gt; dan &lt;code&gt;memory: 512&lt;/code&gt; itu spec paling minimal di Fargate, dan perlu diingat ini dialokasiin di level &lt;strong&gt;task&lt;/strong&gt;, bukan per container. Artinya kalau lo punya lebih dari satu container di task yang sama, semua sharing pool ini. Untuk single container staging cukup, tapi kalau lo mau nambah container lain di task yang sama, naikin spec dulu sebelum deploy.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;networkMode: awsvpc&lt;/code&gt; mandatory buat Fargate, setiap task dapat ENI sendiri.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;logDriver: awslogs&lt;/code&gt; log container lo otomatis masuk ke CloudWatch, sangat membantu waktu debugging.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;environmentFiles&lt;/code&gt; yang nge-load &lt;code&gt;.env&lt;/code&gt; dari S3 yang kita setup di step sebelumnya.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Buat CloudWatch log group dulu sebelum deploy. Ada dua cara:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Via AWS CLI:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Buat log group&lt;/span&gt;
aws logs create-log-group &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--log-group-name&lt;/span&gt; /ecs/nama-service-lo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; ap-southeast-1 &lt;span class="c"&gt;# GANTI INI&lt;/span&gt;

&lt;span class="c"&gt;# Set retention 30 hari supaya ga numpuk dan kena billing&lt;/span&gt;
aws logs put-retention-policy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--log-group-name&lt;/span&gt; /ecs/nama-service-lo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--retention-in-days&lt;/span&gt; 30 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; ap-southeast-1 &lt;span class="c"&gt;# GANTI INI&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Via AWS Console:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Masuk ke CloudWatch &amp;gt; Log groups &amp;gt; Create log group. Isi log group name dengan &lt;code&gt;/ecs/nama-service-lo&lt;/code&gt;, lalu di bagian Retention setting pilih &lt;strong&gt;30 days&lt;/strong&gt;. Kalau lo skip retention policy, log lo bakal disimpan selamanya dan kena billing terus.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Buat Cluster
&lt;/h2&gt;

&lt;p&gt;Masuk ke AWS Console &amp;gt; ECS &amp;gt; Clusters &amp;gt; Create Cluster. Pilih &lt;strong&gt;AWS Fargate&lt;/strong&gt; sebagai infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Setup ALB, Target Group &amp;amp; Service
&lt;/h2&gt;

&lt;p&gt;Sebelum buat service, lo harus siapin ALB dan target group dulu karena lo bakal attach ini waktu buat service di Step selanjutnya.&lt;/p&gt;

&lt;h3&gt;
  
  
  Buat Target Group
&lt;/h3&gt;

&lt;p&gt;Masuk ke EC2 &amp;gt; Target Groups &amp;gt; Create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Target type:&lt;/strong&gt; IP (bukan Instance, karena Fargate pakai awsvpc mode)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protocol:&lt;/strong&gt; HTTP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port:&lt;/strong&gt; sesuaikan dengan port container lo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health check path:&lt;/strong&gt; &lt;code&gt;/&lt;/code&gt; atau endpoint health check lo kalau ada (biasanya gua pake pattern&lt;code&gt;/api/v1/health&lt;/code&gt;) isinya json biasa aja&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Buat ALB
&lt;/h3&gt;

&lt;p&gt;Masuk ke EC2 &amp;gt; Load Balancers &amp;gt; Create &amp;gt; Application Load Balancer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scheme:&lt;/strong&gt; Internet-facing (kalau mau diakses dari luar)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subnets:&lt;/strong&gt; minimal 2 AZ, pilih public subnet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security group:&lt;/strong&gt; allow inbound port 80 dan/atau 443&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Listener:&lt;/strong&gt; forward ke target group yang baru lo buat&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Setelah ALB dan target group siap, baru lo buat service ECS-nya.&lt;/p&gt;

&lt;h3&gt;
  
  
  Buat Service
&lt;/h3&gt;

&lt;p&gt;Masuk ke cluster yang baru lo buat, klik &lt;strong&gt;Create service&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Isi konfigurasi dasar:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Launch type:&lt;/strong&gt; Fargate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task definition:&lt;/strong&gt; pilih yang lo buat di Step 4&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service name:&lt;/strong&gt; kasih nama yang deskriptif&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Desired tasks:&lt;/strong&gt; 1 untuk awal&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Di bagian &lt;strong&gt;Networking:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pilih VPC yang sama dengan RDS lo&lt;/li&gt;
&lt;li&gt;Pilih minimal 2 subnet&lt;/li&gt;
&lt;li&gt;Buat atau pilih security group yang allow inbound dari ALB dan allow outbound ke RDS&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Di bagian &lt;strong&gt;Load balancing:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Pilih &lt;strong&gt;Application Load Balancer&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Pilih ALB yang baru lo buat&lt;/li&gt;
&lt;li&gt;Pilih target group yang sesuai&lt;/li&gt;
&lt;li&gt;Pastiin health check path lo bener&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Klik &lt;strong&gt;Create service&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;ECS bakal langsung pull image dari ECR dan jalanin task pertama lo. Setelah task &lt;code&gt;RUNNING&lt;/code&gt;, ECS otomatis register ke target group dan ALB mulai routing traffic ke container lo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Deploy Ulang Setelah Setup Awal
&lt;/h2&gt;

&lt;p&gt;Step 5 dan 6 itu cuma dilakuin sekali waktu pertama kali setup. Setelah infrastruktur jalan, workflow deploy lo jadi jauh lebih simpel.&lt;/p&gt;

&lt;p&gt;Tiap kali ada update code, lo cukup:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Push image baru ke ECR:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Trigger ECS buat pull image terbaru:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"ap-southeast-1"&lt;/span&gt; &lt;span class="c"&gt;# GANTI INI&lt;/span&gt;
&lt;span class="nv"&gt;CLUSTER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"nama-cluster-lo"&lt;/span&gt; &lt;span class="c"&gt;# GANTI INI&lt;/span&gt;
&lt;span class="nv"&gt;SERVICE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"nama-service-lo"&lt;/span&gt; &lt;span class="c"&gt;# GANTI INI&lt;/span&gt;

aws.exe ecs update-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CLUSTER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVICE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--force-new-deployment&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✅ Service '&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVICE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' updated"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--force-new-deployment&lt;/code&gt; yang bikin ECS stop task lama dan start task baru dengan image &lt;code&gt;latest&lt;/code&gt; yang baru aja lo push. Script ini cuma bisa dijalanin kalau service lo udah exist, makanya setup awal di Step 5 harus selesai dulu.&lt;/p&gt;

&lt;p&gt;Dua script, selesai.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8: Verifikasi
&lt;/h2&gt;

&lt;p&gt;Setelah &lt;code&gt;update-service&lt;/code&gt; jalan, tunggu beberapa menit (biasanya 2-5 menit) dan cek:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;ECS Console&lt;/strong&gt; &amp;gt; cluster lo &amp;gt; service &amp;gt; Tasks, pastiin task statusnya &lt;code&gt;RUNNING&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch Logs&lt;/strong&gt;, cek &lt;code&gt;/ecs/nama-service-lo&lt;/code&gt; buat liat log container lo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target Group&lt;/strong&gt;, cek health check statusnya &lt;code&gt;healthy&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ALB DNS&lt;/strong&gt;, akses DNS ALB lo di browser, harusnya app lo udah jalan&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Kalau task gagal start, logs di task atau logs CloudWatch itu tempat pertama yang harus lo cek. keterangan log nya itu sama persis ketika lo jalanin di lokal seperti log error, warning, info dll&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Beberapa hal yang gua pelajarin dari setup ini di production:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Health check itu krusial&lt;/strong&gt;&lt;br&gt;
Kalau health check path lo salah atau endpoint lo return status bukan 200, ALB bakal terus-terusan drain dan replace task dan ini yang pertama kali gua pikir selalu gagal 2 hari debugging ini ternyata dia nyari yang response 200. Pastiin health check path lo bener sebelum deploy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Security group harus teliti&lt;/strong&gt;&lt;br&gt;
ECS task, ALB, dan RDS punya security group masing-masing. Yang paling sering bikin masalah: ECS task ga bisa konek ke RDS karena security group RDS belum allow inbound dari security group ECS. Cek ini kalau app lo gagal konek ke database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. S3 env file ga otomatis refresh&lt;/strong&gt;&lt;br&gt;
ini juga pain point, Kalau lo update &lt;code&gt;.env&lt;/code&gt; di S3, container yang lagi jalan ga otomatis reload. Lo tetap harus trigger &lt;code&gt;--force-new-deployment&lt;/code&gt; buat apply perubahan env.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Minimal spec dulu&lt;/strong&gt;&lt;br&gt;
Mulai dari 256 CPU / 512 MB memory. Monitor dulu di CloudWatch, baru scale up kalau emang butuh. Jangan langsung pakai spec besar karena Fargate billing per resource yang lo define.&lt;/p&gt;




&lt;h2&gt;
  
  
  Penutup
&lt;/h2&gt;

&lt;p&gt;Setup ini mungkin keliatan banyak langkahnya, tapi setelah semua infrastruktur jalan, workflow deploy harian lo cuma dua script. Simpel dan bisa dikontrol.&lt;/p&gt;

&lt;p&gt;Dari sini, langkah selanjutnya yang natural adalah:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tambah CI/CD supaya ga perlu build dari local (AWS CodePipeline + CodeBuild)&lt;/li&gt;
&lt;li&gt;Setup auto scaling di ECS service&lt;/li&gt;
&lt;li&gt;Pisahin staging dan production environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kalau ada pertanyaan atau ada yang kurang jelas, drop di kolom komentar. Gua bakal jawab sesuai pengalaman gua.&lt;/p&gt;

</description>
      <category>ecs</category>
      <category>aws</category>
      <category>nestjs</category>
      <category>indonesia</category>
    </item>
  </channel>
</rss>
