<?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: Cheulong Sear</title>
    <description>The latest articles on DEV Community by Cheulong Sear (@cheulong).</description>
    <link>https://dev.to/cheulong</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%2F1707766%2F12f15bdd-e5c9-493e-ad69-b9f5a58f8cf6.jpg</url>
      <title>DEV Community: Cheulong Sear</title>
      <link>https://dev.to/cheulong</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cheulong"/>
    <language>en</language>
    <item>
      <title>How to set up k3s on Ubuntu server</title>
      <dc:creator>Cheulong Sear</dc:creator>
      <pubDate>Tue, 03 Feb 2026 07:36:00 +0000</pubDate>
      <link>https://dev.to/cheulong/how-to-set-up-k3s-on-ubuntu-server-173h</link>
      <guid>https://dev.to/cheulong/how-to-set-up-k3s-on-ubuntu-server-173h</guid>
      <description>&lt;h2&gt;
  
  
  What is k3s?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisite
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Servers&lt;/li&gt;
&lt;li&gt;Fixed Registration Address&lt;/li&gt;
&lt;li&gt;Option: Worker node&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Create the control plane
&lt;/h3&gt;

&lt;p&gt;run this script to create a control plane&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -sfL https://get.k3s.io | sh -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create the worker node
&lt;/h3&gt;

&lt;p&gt;To join a worker node to your existing k3s control plane, you need two things from the control-plane node:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The node token&lt;/li&gt;
&lt;li&gt;The control-plane node’s IP or hostname&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  1. Get the node token (on the control-plane node)
&lt;/h3&gt;

&lt;p&gt;Run this on the machine where you installed k3s with your command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo cat /var/lib/rancher/k3s/server/node-token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy this value.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Join the worker node
&lt;/h3&gt;

&lt;p&gt;On the worker node, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -sfL https://get.k3s.io | K3S_URL=https://&amp;lt;CONTROL_PLANE_IP&amp;gt;:6443 K3S_TOKEN=&amp;lt;NODE_TOKEN&amp;gt; sh -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Example:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -sfL https://get.k3s.io | \
  K3S_URL=https://192.168.1.10:6443 \
  K3S_TOKEN=K10f3a9c7c0f2e7c8d9b3b1a2c3d4e5f6::server:abcdef1234567890 \
  sh -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Verify the worker joined
&lt;/h4&gt;

&lt;p&gt;Back on the control-plane node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get nodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME           STATUS   ROLES                  AGE   VERSION
master-1       Ready    control-plane,master   5m    v1.xx.x+k3s
worker-1       Ready    &amp;lt;none&amp;gt;                  1m    v1.xx.x+k3s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Optional copy kubeconfig to personal pc
&lt;/h4&gt;

&lt;p&gt;On the control-plane node&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo cp /etc/rancher/k3s/k3s.yaml /tmp/k3s.yaml
sudo chown $USER:$USER /tmp/k3s.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On your Ubuntu Desktop&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scp user@192.0.0.1:/tmp/k3s.yaml ~/.kube/config
chmod 600 ~/.kube/config

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

&lt;/div&gt;



&lt;p&gt;Edit ONLY the server line&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nano ~/.kube/config

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

&lt;/div&gt;



&lt;p&gt;change&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server: https://127.0.0.1:6443

# to

server: https://&amp;lt;control-plane-ip&amp;gt;:6443

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

&lt;/div&gt;



&lt;p&gt;verification&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl config get-contexts
kubectl cluster-info
kubectl get nodes

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Troubleshooting
&lt;/h3&gt;

&lt;h4&gt;
  
  
  hostname collusion
&lt;/h4&gt;

&lt;p&gt;On control plane, Run :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl status k3s-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if you see &lt;code&gt;403 unable to verify password for node ubuntu-server&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
you might need to change worker-node's hostname&lt;br&gt;
on worker node&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# change hostname to worker-1

sudo systemctl stop k3s-agent || true
sudo /usr/local/bin/k3s-agent-uninstall.sh || true
sudo rm -rf /var/lib/rancher/k3s
sudo rm -rf /etc/rancher

sudo hostnamectl set-hostname worker-1
exec bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the CONTROL PLANE — remove the old node entry&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl delete node ubuntu-server || true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also verify the token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo cat /var/lib/rancher/k3s/server/node-token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rejoin the worker with the correct token&lt;br&gt;
On the worker node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -sfL https://get.k3s.io | \
  K3S_URL=https://&amp;lt;CONTROL_PLANE_IP&amp;gt;:6443 \
  K3S_TOKEN=&amp;lt;NODE_TOKEN&amp;gt; \
  sh -

sudo systemctl restart k3s-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;=== Done ===&lt;/p&gt;

&lt;p&gt;Leave a comment if you have any questions.&lt;/p&gt;

&lt;p&gt;===========&lt;br&gt;
&lt;strong&gt;Please keep in touch&lt;/strong&gt;&lt;br&gt;
  &lt;a href="//cheulongsear.dev"&gt;Portfolio&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.linkedin.com/in/cheulongsear/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://github.com/cheulong" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.youtube.com/@allo-devops" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>linux</category>
      <category>homelab</category>
      <category>k3s</category>
    </item>
    <item>
      <title>Your first gRPC API in Node.js</title>
      <dc:creator>Cheulong Sear</dc:creator>
      <pubDate>Mon, 02 Feb 2026 05:08:00 +0000</pubDate>
      <link>https://dev.to/cheulong/your-first-grpc-api-in-nodejs-1nke</link>
      <guid>https://dev.to/cheulong/your-first-grpc-api-in-nodejs-1nke</guid>
      <description>&lt;h2&gt;
  
  
  Convert Rest API to gRPC
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why use gRPC?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;gRPC&lt;/strong&gt; is a high-performance, open-source universal RPC framework that runs natively on microservices and serverless architectures.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Rest API
&lt;/h4&gt;

&lt;p&gt;Here is a simple example of a Rest API of restaurant service that return list of restaurants based on restaurant ids.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;restaurant&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;  

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;restaurantsData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;res_001&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Spicy Garden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cuisine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Indian&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;New York&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;4.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;res_002&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sushi Zen&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cuisine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Japanese&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;San Francisco&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;4.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;res_003&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pasta Palace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cuisine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Italian&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Chicago&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;4.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;res_004&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Burger Hub&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cuisine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;American&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Austin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;res_005&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dragon Wok&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cuisine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Chinese&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Seattle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;4.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/restaurants/bulk&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;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;restaurantIds&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;restaurantsData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;restaurantIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Server is running on port 3001&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;ul&gt;
&lt;li&gt;To convert this Rest API to gRPC, we need to define a &lt;strong&gt;.proto file&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;.proto&lt;/code&gt; file is a language-neutral, platform-neutral, and protocol-neutral interface definition language (IDL) that defines the service and message formats.&lt;br&gt;
  See the doc for more details: &lt;a href="https://protobuf.dev/programming-guides/proto3/" rel="noopener noreferrer"&gt;https://protobuf.dev/programming-guides/proto3/&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;restaurants.proto&lt;/span&gt;

  &lt;span class="na"&gt;syntax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"proto3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;restaurants&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;service&lt;/span&gt; &lt;span class="n"&gt;RestaurantService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;GetRestaurantsBulk&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BulkRestaurantRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BulkRestaurantResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;BulkRestaurantRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;repeated&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;restaurantIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;Restaurant&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;cuisine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="na"&gt;rating&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="na"&gt;isOpen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;BulkRestaurantResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;repeated&lt;/span&gt; &lt;span class="n"&gt;Restaurant&lt;/span&gt; &lt;span class="na"&gt;restaurants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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;ul&gt;
&lt;li&gt;a &lt;code&gt;gRPC server&lt;/code&gt; that replaces your Express route&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Install dependencies
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  bun add @grpc/grpc-js @grpc/proto-loader
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;restaurant&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;grpc&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;grpc&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@grpc/grpc-js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;protoLoader&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@grpc/proto-loader&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PROTO_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;restaurants.proto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Load proto&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;packageDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;protoLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PROTO_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;keepCase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;longs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;enums&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;oneofs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;restaurantProto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;grpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadPackageDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;packageDefinition&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;restaurants&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Mock data (same as Express)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;restaurantsData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;res_001&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Spicy Garden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cuisine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Indian&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;New York&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;4.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;res_002&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sushi Zen&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cuisine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Japanese&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;San Francisco&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;4.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;res_003&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pasta Palace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cuisine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Italian&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Chicago&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;4.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;res_004&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Burger Hub&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cuisine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;American&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Austin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;res_005&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dragon Wok&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cuisine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Chinese&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Seattle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;4.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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;span class="c1"&gt;// gRPC method implementation&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getRestaurantsBulk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;restaurantIds&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;restaurants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;restaurantsData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;restaurantIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;restaurants&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Create server&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;grpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;restaurantProto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RestaurantService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;GetRestaurantsBulk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;getRestaurantsBulk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Start server&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bindAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.0.0:50051&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;grpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ServerCredentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createInsecure&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error binding server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gRPC Server running on port &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&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;ul&gt;
&lt;li&gt;Create a &lt;code&gt;gRPC client&lt;/code&gt; that replaces your Express client that returns list of restaurants based on restaurant ids that user visited.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;grpc&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;grpc&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@grpc/grpc-js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;protoLoader&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@grpc/proto-loader&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Load proto&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PROTO_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;restaurants.proto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;packageDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;protoLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PROTO_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;keepCase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;longs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;enums&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;oneofs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;restaurantProto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;grpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadPackageDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;packageDefinition&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;restaurants&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;restaurantClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;restaurantProto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RestaurantService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localhost:50051&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;grpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createInsecure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user_001&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;john.doe@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;vistedRestaurant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;res_001&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;res_002&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;res_003&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;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users/:id/visit-restaurants&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userDetailData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;restaurantClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetRestaurantsBulk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;restaurantIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userDetailData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;vistedRestaurant&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;span class="na"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;grpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ServiceError&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gRPC error&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;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;visitedRestaurantsDetail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;restaurants&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;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Server is running on port 3000&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;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Express&lt;/th&gt;
&lt;th&gt;gRPC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;POST /restaurants/bulk&lt;/td&gt;
&lt;td&gt;GetRestaurantsBulk RPC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;req.body.restaurantIds&lt;/td&gt;
&lt;td&gt;call.request.restaurantIds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;res.json(result)&lt;/td&gt;
&lt;td&gt;callback(null, { restaurants })&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON over HTTP&lt;/td&gt;
&lt;td&gt;Protobuf over HTTP/2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Other Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;: gRPC is faster than REST because it uses binary encoding and HTTP/2, which allows for better compression and faster data transfer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: gRPC supports authentication and authorization, which makes it more secure than REST.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: gRPC is designed to handle high traffic and large data sets, which makes it more scalable than REST.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Drawbacks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Learning Curve&lt;/strong&gt;: gRPC has a steeper learning curve than REST, which can make it more difficult to learn and implement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tooling&lt;/strong&gt;: gRPC has limited tooling support compared to REST, which can make it more difficult to debug and test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility&lt;/strong&gt;: gRPC is less flexible than REST, which can make it more difficult to adapt to changing requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;gRPC is a powerful and efficient way to build microservices, but it is not a one-size-fits-all solution. It is best used for high-performance, low-latency, and high-throughput applications.&lt;/p&gt;

&lt;p&gt;=== Done ===&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/cheulong/grpc-nodejs" rel="noopener noreferrer"&gt;Code for this article&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Leave a comment if you have any questions.&lt;/p&gt;

&lt;p&gt;===========&lt;br&gt;
&lt;strong&gt;Please keep in touch&lt;/strong&gt;&lt;br&gt;
  &lt;a href="//cheulongsear.dev"&gt;Portfolio&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.linkedin.com/in/cheulongsear/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://github.com/cheulong" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.youtube.com/@allo-devops" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;/p&gt;

</description>
      <category>grpc</category>
      <category>node</category>
      <category>api</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Justfile make your life easier</title>
      <dc:creator>Cheulong Sear</dc:creator>
      <pubDate>Sun, 01 Feb 2026 07:48:18 +0000</pubDate>
      <link>https://dev.to/cheulong/justfile-make-your-life-easier-n8f</link>
      <guid>https://dev.to/cheulong/justfile-make-your-life-easier-n8f</guid>
      <description>&lt;h2&gt;
  
  
  What is &lt;code&gt;Just&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;just&lt;/code&gt; is a handy way to save and run project-specific commands.&lt;br&gt;
Commands, called &lt;code&gt;recipes&lt;/code&gt;, are stored in a file called &lt;code&gt;justfile&lt;/code&gt; with syntax inspired by make.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why use &lt;code&gt;Just&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;just&lt;/code&gt; gives you cleaner, safer, more portable project commands than ad-hoc shell scripts or long README copy-paste commands.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;just&lt;/code&gt; has a ton of useful features, and many improvements over make:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;just is a command runner, not a build system, so it avoids much of make's complexity and idiosyncrasies. No need for .PHONY recipes!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Linux, MacOS, Windows, and other reasonable unices are supported with no additional dependencies. (Although if your system doesn't have an sh, you'll need to choose a different shell.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Errors are specific and informative, and syntax errors are reported along with their source context.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recipes can accept command line arguments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wherever possible, errors are resolved statically. Unknown recipes and circular dependencies are reported before anything runs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;just loads .env files, making it easy to populate environment variables.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recipes can be listed from the command line.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Command line completion scripts are available for most popular shells.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recipes can be written in arbitrary languages, like Python or NodeJS.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And much more!&lt;/p&gt;
&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;just&lt;/code&gt; should run on any system with a reasonable sh, including Linux, MacOS, and the BSDs.&lt;/p&gt;

&lt;p&gt;For Ubuntu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install just
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;Here is the example of &lt;code&gt;just&lt;/code&gt; with &lt;code&gt;vitest&lt;/code&gt; in &lt;code&gt;bun&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# sum.ts

console.log('environment is', process.env.NODE_ENV);

export const sum = (a: number, b: number) =&amp;gt; {
    return a + b;
}

console.log(sum(1, 2));
&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;# sum.test.ts

import { expect, test } from 'vitest'
import { sum } from './sum.ts'

test('adds 1 + 2 to equal 3', () =&amp;gt; {
  expect(sum(1, 2)).toBe(3)
})
&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;  "scripts": {
    "start": "bun run sum.ts",
    "test": "vitest"
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;justfile&lt;/code&gt; in the root directory of the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;default:
    just --list --unsorted

start:
    bun run start

test:
    vitest

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

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;just&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;just
&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;just start
&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;just test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Other Arguments
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Env
&lt;/h3&gt;

&lt;p&gt;For example, if your .env file contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASE_ADDRESS=localhost:6379
SERVER_PORT=1337
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And your justfile contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set dotenv-load

serve:
  @echo "Starting server with database $DATABASE_ADDRESS on port $SERVER_PORT…"
  ./server --database $DATABASE_ADDRESS --port $SERVER_PORT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;just serve will output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ just serve
Starting server with database localhost:6379 on port 1337…
./server --database $DATABASE_ADDRESS --port $SERVER_PORT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;### Export&lt;/p&gt;

&lt;p&gt;The export setting causes all just variables to be exported as environment variables. Defaults to false.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set export

a := "hello"

@foo b:
  echo $a
  echo $b
&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;$ just foo goodbye
hello
goodbye
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Positional Arguments
&lt;/h3&gt;

&lt;p&gt;If positional-arguments is true, recipe arguments will be passed as positional arguments to commands. For linewise recipes, argument $0 will be the name of the recipe.&lt;/p&gt;

&lt;p&gt;For example, running this recipe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set positional-arguments

@foo bar:
  echo $0
  echo $1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Will produce the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ just foo hello
foo
hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;=== Done ===&lt;/p&gt;

&lt;p&gt;Leave a comment if you have any questions.&lt;/p&gt;

&lt;p&gt;===========&lt;br&gt;
&lt;strong&gt;Please keep in touch&lt;/strong&gt;&lt;br&gt;
  &lt;a href="//cheulongsear.dev"&gt;Portfolio&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.linkedin.com/in/cheulongsear/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://github.com/cheulong" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.youtube.com/@allo-devops" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;/p&gt;

</description>
      <category>just</category>
      <category>ubuntu</category>
      <category>programming</category>
      <category>cli</category>
    </item>
    <item>
      <title>End-to-End DevSecOps Project (Movies Finder)</title>
      <dc:creator>Cheulong Sear</dc:creator>
      <pubDate>Sun, 23 Nov 2025 14:18:39 +0000</pubDate>
      <link>https://dev.to/cheulong/end-to-end-devsecops-project-movies-finder-5bap</link>
      <guid>https://dev.to/cheulong/end-to-end-devsecops-project-movies-finder-5bap</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Project: End-to-End DevSecOps Project (Movies Finder)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt; While the visible application is a React-based Movie Finder (consuming TMDB API), this project serves as a comprehensive proof-of-concept for a production-grade DevSecOps lifecycle. It demonstrates the automated delivery of a secure, tested, and monitored web application onto a baremetal Kubernetes cluster using GitOps principles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Goal&lt;/strong&gt; To architect a "Zero-Touch" delivery pipeline that automates the build, testing, security scanning, and deployment processes, ensuring that only high-quality, secure code reaches production without manual intervention.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tech Stack &amp;amp; Tools
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Infrastructure &amp;amp; GitOps:&lt;/strong&gt; Kubernetes, Docker, Helm, ArgoCD, Baremetal Homelab&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CI/CD &amp;amp; Release:&lt;/strong&gt; GitLab CI/CD, Release-it, Commitizen, Husky, Lint-Staged&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Observability Stack:&lt;/strong&gt; OpenTelemetry, Prometheus, Grafana, Alloy, Alertmanager, Slack&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security (DevSecOps):&lt;/strong&gt; Trivy, OWASP ZAP, Gitleaks, Sealed-Secrets, Syft (SBOM)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testing &amp;amp; QA:&lt;/strong&gt; Playwright (E2E), Vitest (Unit), SonarCloud&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Key Results &amp;amp; Achievements
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Optimized Release Cycle:&lt;/strong&gt; Automated the entire delivery chain, reducing deployment turnaround time by 50% (from 40m to 20m) while adding comprehensive testing and security stages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;360° Observability:&lt;/strong&gt; Implemented the "Grafana Alloy" &amp;amp; OpenTelemetry stack to correlate metrics and logs, providing real-time performance monitoring and instant Slack alerts for node resource exhaustion.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automated Compliance Gates:&lt;/strong&gt; Shifted security left by integrating Trivy (container scanning) and Dependency Checkinto the CI pipeline, automatically blocking builds with critical CVEs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Zero-Downtime Deployment:&lt;/strong&gt; Leveraged ArgoCD to manage state drift and ensure seamless application updates via GitOps.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check full article: &lt;a href="https://cheulongsear.dev/projects/deploying-movies-finder" rel="noopener noreferrer"&gt;https://cheulongsear.dev/projects/deploying-movies-finder&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Leave a comment if you have any questions.&lt;/p&gt;

&lt;p&gt;===========&lt;br&gt;
&lt;strong&gt;Please keep in touch&lt;/strong&gt;&lt;br&gt;
  &lt;a href="//cheulongsear.dev"&gt;Portfolio&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.linkedin.com/in/cheulongsear/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://github.com/cheulong" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.youtube.com/@moshi-moshi-devops" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>homelab</category>
      <category>sre</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>How to set up Slack notifications from GitLab CI/CD using the Slack API</title>
      <dc:creator>Cheulong Sear</dc:creator>
      <pubDate>Thu, 30 Oct 2025 13:04:43 +0000</pubDate>
      <link>https://dev.to/cheulong/how-to-set-up-slack-notifications-from-gitlab-cicd-using-the-slack-api-h5f</link>
      <guid>https://dev.to/cheulong/how-to-set-up-slack-notifications-from-gitlab-cicd-using-the-slack-api-h5f</guid>
      <description>&lt;p&gt;&lt;a id="readme-top"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Learn how to integrate Slack with GitLab CI/CD to receive real-time build and deployment notifications directly in your Slack channels. This step-by-step guide walks you through creating a Slack app, configuring the Slack API, and setting up GitLab pipeline jobs to send custom messages for successful builds, failures, and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Slack App
&lt;/h3&gt;

&lt;p&gt;1.Go to &lt;a href="https://api.slack.com/apps" rel="noopener noreferrer"&gt;https://api.slack.com/apps&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;After signin &lt;/p&gt;

&lt;p&gt;2.Click Create New App → From Scratch&lt;/p&gt;

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

&lt;p&gt;3.Give it a name (e.g., GitLab CI Notifier)&lt;br&gt;
4.Choose your workspace&lt;/p&gt;

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

&lt;p&gt;5.Create App&lt;/p&gt;

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

&lt;p&gt;You can add App Icon and App name here&lt;/p&gt;

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

&lt;p&gt;1.In the left sidebar, go to Features → Incoming Webhooks&lt;/p&gt;

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

&lt;p&gt;2.Toggle Activate Incoming Webhooks → ON&lt;/p&gt;

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

&lt;p&gt;3.Click Add New Webhook to Workspace&lt;br&gt;
4.Choose a channel or private group where messages should be sent&lt;/p&gt;

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

&lt;p&gt;5.Copy the Webhook URL:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Store the Webhook URL in GitLab
&lt;/h3&gt;

&lt;p&gt;1.Go to your GitLab project → Settings → CI/CD → Variables&lt;/p&gt;

&lt;p&gt;2.Add a variable:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg3q2i6zlzvax1m1ojq1c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg3q2i6zlzvax1m1ojq1c.png" alt="image10.png" width="381" height="950"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Edit your .gitlab-ci.yaml
&lt;/h3&gt;

&lt;p&gt;Add a job that sends a message to Slack:&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;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;slack_notify&lt;/span&gt;

&lt;span class="na"&gt;notify_slack&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;slack_notify&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;linux-homelab-general-vm&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpine:latest&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apk add --no-cache curl&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;MESSAGE="✅ Pipeline $CI_PIPELINE_STATUS for project $CI_PROJECT_NAME on branch $CI_COMMIT_REF_NAME – $CI_COMMIT_SHORT_SHA"&lt;/span&gt;
      &lt;span class="s"&gt;echo "Sending Slack notification: $MESSAGE"&lt;/span&gt;
      &lt;span class="s"&gt;curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$MESSAGE\"}" "$SLACK_WEBHOOK"&lt;/span&gt;
  &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;=== Done ===&lt;/p&gt;

&lt;p&gt;(back to top)&lt;/p&gt;

&lt;p&gt;Leave a comment if you have any questions.&lt;/p&gt;

&lt;p&gt;===========&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please keep in touch&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="//cheulongsear.dev"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/cheulongsear/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/cheulong" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/@moshi-moshi-devops" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gitlab</category>
      <category>cicd</category>
      <category>slack</category>
      <category>automation</category>
    </item>
    <item>
      <title>Setup cert-manager in local K8s cluster using nginx-ingress &amp; Cloudflare DNS</title>
      <dc:creator>Cheulong Sear</dc:creator>
      <pubDate>Wed, 29 Oct 2025 22:45:29 +0000</pubDate>
      <link>https://dev.to/cheulong/setup-cert-manager-in-local-k8s-cluster-using-nginx-ingress-cloudflare-dns-a9c</link>
      <guid>https://dev.to/cheulong/setup-cert-manager-in-local-k8s-cluster-using-nginx-ingress-cloudflare-dns-a9c</guid>
      <description>&lt;p&gt;&lt;a id="readme-top"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Apply nginx deployment&lt;/li&gt;
&lt;li&gt;Apply nginx clusterIp service&lt;/li&gt;
&lt;li&gt;Install ingress-nginx&lt;/li&gt;
&lt;li&gt;Setup Local domain name pointing to Load Balancer IP&lt;/li&gt;
&lt;li&gt;Create the Cloudflare API token&lt;/li&gt;
&lt;li&gt;Setup cert-manager&lt;/li&gt;
&lt;li&gt;Enable TLS on ingress&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Prerequisite:
&lt;/h4&gt;

&lt;p&gt;MetalLB is already set up in this local k8s cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.Apply nginx deployment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;lt;&amp;lt;EOF &amp;gt; nginx-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.27
        ports:
        - containerPort: 80
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;kubectl apply -f nginx-deploy.yaml&lt;/code&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  2.Apply nginx clusterIp service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;lt;&amp;lt;EOF &amp;gt; nginx-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: ClusterIP
  selector:
    app: nginx
  ports:
    - port: 80
      targetPort: 80
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;kubectl apply -f nginx-svc.yaml&lt;/code&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  3.Install ingress-nginx
&lt;/h3&gt;

&lt;p&gt;Next we will install ingress-nginx using helm&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx --create-namespace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;set up ingress pointing to nginx service&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;lt;&amp;lt;EOF &amp;gt; ingress-svc.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginxservice
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - host: nginx.cheulong-homelab.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
                name: nginx-service
                port:
                  number: 80

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

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;kubectl apply -f ingress-svc.yaml&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.Setup Local domain name pointing to Load Balancer IP
&lt;/h3&gt;

&lt;p&gt;If you go to &lt;code&gt;http://192.168.186.241&lt;/code&gt; now you will see 404 error, it means ingress works correctly, we need to setup domain name to point to that IP.&lt;/p&gt;

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

&lt;p&gt;Type &lt;code&gt;sudo nano /etc/hosts&lt;/code&gt; and add like below&lt;/p&gt;

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

&lt;p&gt;now go to &lt;code&gt;http://nginx.cheulong-homelab.com&lt;/code&gt; &lt;/p&gt;

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

&lt;p&gt;Next step, we need to use cert-manager to generate the certificate for this domain.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.Create the Cloudflare API token
&lt;/h3&gt;

&lt;p&gt;1.Log in to Cloudflare Dashboard &lt;a href="https://dash.cloudflare.com/" rel="noopener noreferrer"&gt;https://dash.cloudflare.com/&lt;/a&gt;&lt;br&gt;
2.Click your profile icon (top right) → “My Profile”&lt;br&gt;
3.Go to API Tokens → click “Create Token”&lt;br&gt;
4.Choose “Edit zone DNS” template&lt;br&gt;
5.Under “Permissions”, make sure it includes:&lt;br&gt;
&lt;code&gt;Zone | DNS | Edit&lt;/code&gt;&lt;br&gt;
6.Under “Zone Resources”, choose: &lt;code&gt;Include | All zones&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;7.Click Continue to summary → Create Token&lt;br&gt;
8.Copy the generated token and keep it safe, you’ll only see it once.&lt;/p&gt;
&lt;h3&gt;
  
  
  6.Setup cert-manager
&lt;/h3&gt;

&lt;p&gt;1.deploy the cert-manager using helm&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm install \
  cert-manager oci://quay.io/jetstack/charts/cert-manager \
  --version v1.19.1 \
  --namespace cert-manager \
  --create-namespace \
  --set crds.enabled=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2.deploy letsencrypt cluster issuer&lt;/p&gt;

&lt;p&gt;Create secret for cloudflare token api&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl create secret generic cloudflare-api-token-secret \
  --from-literal=api-token='cfapi_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  -n cert-manager

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

&lt;/div&gt;



&lt;p&gt;Let's encrypt will use cloudflare dns to verify the ownership.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;lt;&amp;lt;EOF &amp;gt; cluster-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-dns
spec:
  acme:
    email: searcheulong@gmail.com
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-dns-key
    solvers:
    - dns01:
        cloudflare:
          apiTokenSecretRef:
            name: cloudflare-api-token-secret
            key: api-token
    # Add additional solvers here if needed
    # - http01:
    #     ingress:
    #       ingressClassName: nginx
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;kubectl apply -f cluster-issuer.yaml&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;3.deploy the certificate&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;lt;&amp;lt;EOF &amp;gt; cert.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: cheulong-nginx-cert
  namespace: default
spec:
  secretName: cheulong-nginx-tls
  dnsNames:
    - nginx.cheulong-homelab.com
  issuerRef:
    name: letsencrypt-dns
    kind: ClusterIssuer
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;kubectl apply -f cert.yaml&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;You can check the detail&lt;/p&gt;

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

&lt;p&gt;The tls also was created &lt;/p&gt;

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

&lt;h3&gt;
  
  
  7.Enable TLS on ingress
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;lt;&amp;lt;EOF &amp;gt; ingress-svc.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginxservice
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  tls: 
  - hosts:
    - nginx.cheulong-homelab.com
    secretName: cheulong-nginx-tls
  ingressClassName: nginx
  rules:
  - host: nginx.cheulong-homelab.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
                name: nginx-service
                port:
                  number: 80

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

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;kubectl apply -f ingress-svc.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now if you type &lt;code&gt;https://nginx.cheulong-homelab.com/&lt;/code&gt;, it will connect successfully.&lt;/p&gt;

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

&lt;p&gt;=== Done ===&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/cheulong/devops/tree/main/k8s/cert-manager" rel="noopener noreferrer"&gt;Code for this article&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(back to top)&lt;/p&gt;

&lt;p&gt;Leave a comment if you have any questions.&lt;/p&gt;

&lt;p&gt;===========&lt;br&gt;
&lt;strong&gt;Please keep in touch&lt;/strong&gt;&lt;br&gt;
  &lt;a href="//cheulongsear.dev"&gt;Portfolio&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.linkedin.com/in/cheulongsear/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://github.com/cheulong" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.youtube.com/@moshi-moshi-devops" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>cloudflare</category>
      <category>linux</category>
      <category>ingress</category>
    </item>
    <item>
      <title>Expose the Kubernetes Load Balancer on Bare Metal using MetalLB</title>
      <dc:creator>Cheulong Sear</dc:creator>
      <pubDate>Tue, 14 Oct 2025 18:54:49 +0000</pubDate>
      <link>https://dev.to/cheulong/expose-the-kubernetes-load-balancer-on-bare-metal-using-metallb-9hh</link>
      <guid>https://dev.to/cheulong/expose-the-kubernetes-load-balancer-on-bare-metal-using-metallb-9hh</guid>
      <description>&lt;p&gt;&lt;a id="readme-top"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article will show you how to install MetalLB in K8s and expose a load balancer in your network.&lt;br&gt;
Please refer to the &lt;a href="https://metallb.io/installation/" rel="noopener noreferrer"&gt;official&lt;/a&gt; doc for more detail.&lt;/p&gt;
&lt;h2&gt;
  
  
  Preparation
&lt;/h2&gt;

&lt;p&gt;If you’re using kube-proxy in IPVS mode, since Kubernetes v1.14.2 you have to enable strict ARP mode.&lt;br&gt;
&lt;code&gt;run&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get configmap kube-proxy -n kube-system -o yaml | \
sed -e "s/strictARP: false/strictARP: true/" | \
kubectl diff -f - -n kube-system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if you see something like this&lt;/p&gt;

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

&lt;p&gt;then run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get configmap kube-proxy -n kube-system -o yaml | \
sed -e "s/strictARP: false/strictARP: true/" | \
kubectl apply -f - -n kube-system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installation with Helm
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm repo add metallb https://metallb.github.io/metallb
helm install metallb metallb/metallb --create-namespace -n metallb-system 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  L2 configuration
&lt;/h2&gt;

&lt;p&gt;create &lt;code&gt;metallb-config.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default-ip-pool
  namespace: metallb-system
spec:
  addresses:
  - 192.168.186.241-192.168.186.250
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: default
  namespace: metallb-system
spec:
  ipAddressPools:
  - default-ip-pool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;run &lt;code&gt;kubectl apply -f metallb-config.yaml&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Note&lt;/strong&gt;: ip range need to be inside &lt;code&gt;192.168.186.0/24&lt;/code&gt; range&lt;/p&gt;
&lt;h3&gt;
  
  
  Check if it works
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;kubectl get ipaddresspools -n metallb-system&lt;/code&gt;&lt;br&gt;
&lt;code&gt;kubectl get l2advertisement -n metallb-system&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Deploy nginx
&lt;/h2&gt;

&lt;p&gt;run &lt;code&gt;kubectl create deploy nginx --image nginx:latest&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Expose nginx
&lt;/h2&gt;
&lt;h3&gt;
  
  
  using load balancer
&lt;/h3&gt;

&lt;p&gt;run &lt;code&gt;kubectl expose deploy nginx --port 80 --type LoadBalancer&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cheulong-sear@cheulong-linux:~$ k get svc
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)        AGE
kubernetes   ClusterIP      10.96.0.1       &amp;lt;none&amp;gt;            443/TCP        16d
nginx        LoadBalancer   10.106.24.222   192.168.186.242   80:32454/TCP   69s
test-nginx   NodePort       10.99.173.211   &amp;lt;none&amp;gt;            80:32544/TCP   16d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;go to &lt;code&gt;192.168.186.242&lt;/code&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  using Ingress Nginx
&lt;/h3&gt;

&lt;p&gt;Install ingress nginx using helm&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx --create-namespace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Expose ClusterIp Service
&lt;/h4&gt;

&lt;p&gt;run &lt;code&gt;kubectl expose deploy nginx --port 80&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  create manifest to route to nginx service
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx
            port:
              number: 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;kubectl apply ingress.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl -n ingress-nginx get svc ingress-nginx-controller&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                       TYPE           CLUSTER-IP    EXTERNAL-IP       PORT(S)                      AGE
ingress-nginx-controller   LoadBalancer   10.97.61.44   192.168.186.241   80:31540/TCP,443:32342/TCP   16m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;go to &lt;code&gt;192.168.186.242&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;=== Done ===&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/cheulong/devops/tree/main/k8s/metallb" rel="noopener noreferrer"&gt;Code for this article&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(back to top)&lt;/p&gt;

&lt;p&gt;Leave a comment if you have any questions.&lt;/p&gt;

&lt;p&gt;===========&lt;br&gt;
&lt;strong&gt;Please keep in touch&lt;/strong&gt;&lt;br&gt;
  &lt;a href="//cheulongsear.dev"&gt;Portfolio&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.linkedin.com/in/cheulongsear/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://github.com/cheulong" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.youtube.com/@allo-devops" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;/p&gt;

</description>
      <category>homelab</category>
      <category>kubernetes</category>
      <category>devops</category>
      <category>metallb</category>
    </item>
    <item>
      <title>Fix audio not working on Ubuntu 24.04.3 (at least in my case)</title>
      <dc:creator>Cheulong Sear</dc:creator>
      <pubDate>Sun, 05 Oct 2025 16:27:10 +0000</pubDate>
      <link>https://dev.to/cheulong/fix-audio-not-working-on-ubuntu-24043-at-least-in-my-case-1e4j</link>
      <guid>https://dev.to/cheulong/fix-audio-not-working-on-ubuntu-24043-at-least-in-my-case-1e4j</guid>
      <description>&lt;p&gt;&lt;a id="readme-top"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Recently I installed a new Ubuntu, after that my wired earphones didn't work anymore, I spent a lot of time fixing it. Here is the step that I did. It might depend on the device; in my case, it is an Asus ROG Strix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Check if the device is detected
&lt;/h2&gt;

&lt;p&gt;Run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lspci | grep -i audio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00:1f.3 Audio device: Intel Corporation CM238 HD Audio Controller (rev 31)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If nothing appears, your kernel might not detect it → you may need firmware support.&lt;/p&gt;

&lt;p&gt;If it does appear, continue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Check sound card listing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aplay -l
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see something like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ty3kz2pu6iy7cr4mf3v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ty3kz2pu6iy7cr4mf3v.png" alt="soundcard-listing" width="778" height="323"&gt;&lt;/a&gt;&lt;br&gt;
If you see “no soundcards found”, try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo alsa force-reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then check again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Open sound settings
&lt;/h2&gt;



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

&lt;/div&gt;



&lt;p&gt;Install if missing: &lt;code&gt;sudo apt install pavucontrol&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Reload ALSA and PulseAudio
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo alsa force-reload
systemctl --user restart pulseaudio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use PipeWire (Ubuntu 24.04 does):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;systemctl --user restart pipewire pipewire-pulse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Check kernel module
&lt;/h2&gt;

&lt;p&gt;Make sure the driver is loaded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lsmod | grep snd_hda_intel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If missing, try loading it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo modprobe snd_hda_intel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then confirm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dmesg | grep snd_hda_intel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for errors like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cannot find codec
# or
device initialization failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6: Fix possible model issue
&lt;/h2&gt;

&lt;p&gt;Try editing ALSA config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/modprobe.d/alsa-base.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this line at the end&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;options snd-hda-intel model=alc295
options snd-hda-intel model=auto
options snd-hda-intel model=dell-headset-multi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;then reboot&lt;/p&gt;

&lt;p&gt;For me, it works&lt;/p&gt;

&lt;p&gt;(back to top)&lt;/p&gt;

&lt;p&gt;Leave a comment if you have any questions.&lt;/p&gt;

&lt;p&gt;===========&lt;br&gt;
&lt;strong&gt;Please keep in touch&lt;/strong&gt;&lt;br&gt;
  &lt;a href="//cheulongsear.dev"&gt;Portfolio&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.linkedin.com/in/cheulongsear/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://github.com/cheulong" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.youtube.com/@allo-devops" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;/p&gt;

</description>
      <category>audio</category>
      <category>ubuntu</category>
      <category>linux</category>
      <category>earphone</category>
    </item>
    <item>
      <title>Basic Linux command (awk)</title>
      <dc:creator>Cheulong Sear</dc:creator>
      <pubDate>Sat, 16 Aug 2025 11:46:00 +0000</pubDate>
      <link>https://dev.to/cheulong/basic-linux-command-awk-3h0k</link>
      <guid>https://dev.to/cheulong/basic-linux-command-awk-3h0k</guid>
      <description>&lt;p&gt;&lt;strong&gt;awk&lt;/strong&gt; pattern scanning and processing language&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;awk&lt;/strong&gt; command may not be pre-installed on all Linux distributions. To install it use&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;# Debian/Ubuntu&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install awk&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To use awk, just type &lt;code&gt;awk '{command}' filename&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here I'm using &lt;code&gt;movies.txt&lt;/code&gt; as example&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;cat &lt;/span&gt;movies.txt
&lt;span class="nt"&gt;----&lt;/span&gt;
Title   Release Year    Budget  Profit &lt;span class="o"&gt;(&lt;/span&gt;Approx.&lt;span class="o"&gt;)&lt;/span&gt;        Studio  Genre
Avatar  2009    &lt;span class="nv"&gt;$237&lt;/span&gt; million    ~&lt;span class="nv"&gt;$2&lt;/span&gt;.7 billion   20th Century Fox        Sci-Fi / Action
Avengers: Endgame       2019    &lt;span class="nv"&gt;$356&lt;/span&gt; million    ~&lt;span class="nv"&gt;$2&lt;/span&gt;.2 billion   Marvel Studios  Superhero / Action
The Dark Knight 2008    &lt;span class="nv"&gt;$185&lt;/span&gt; million    ~&lt;span class="nv"&gt;$894&lt;/span&gt; million   Warner Bros.    Superhero / Crime Thriller
Titanic 1997    &lt;span class="nv"&gt;$200&lt;/span&gt; million    ~&lt;span class="nv"&gt;$2&lt;/span&gt;.1 billion   20th Century Fox        Romance / Drama
Jurassic World  2015    &lt;span class="nv"&gt;$150&lt;/span&gt; million    ~&lt;span class="nv"&gt;$1&lt;/span&gt;.3 billion   Universal Pictures      Sci-Fi / Adventure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Print all lines
&lt;/h4&gt;

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

&lt;h4&gt;
  
  
  Search Lines with a Keyword
&lt;/h4&gt;

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

&lt;h4&gt;
  
  
  Print Specific Columns
&lt;/h4&gt;

&lt;p&gt;By default, awk uses space as separator, here I use tab as separator, so I need to set it as the separator.&lt;/p&gt;

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

&lt;h4&gt;
  
  
  Use of NR built-in variables to display line number
&lt;/h4&gt;

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

&lt;h4&gt;
  
  
  Use of NR built-in variables to display line 2 to 4
&lt;/h4&gt;

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

&lt;h4&gt;
  
  
  Use of NF built-in variables to display last field)
&lt;/h4&gt;

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

&lt;h4&gt;
  
  
  Print nonempty line with line number, first, second and last columns separated by &lt;code&gt;-&lt;/code&gt;
&lt;/h4&gt;

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

&lt;p&gt;Leave a comment if you have any questions.&lt;/p&gt;

&lt;p&gt;===========&lt;br&gt;
&lt;strong&gt;Please keep in touch&lt;/strong&gt;&lt;br&gt;
  &lt;a href="//cheulongsear.dev"&gt;Portfolio&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.linkedin.com/in/cheulongsear/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://github.com/cheulong" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.youtube.com/@allo-devops" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ubuntu</category>
      <category>linux</category>
      <category>homelab</category>
      <category>cli</category>
    </item>
    <item>
      <title>Basic Linux command (sed)</title>
      <dc:creator>Cheulong Sear</dc:creator>
      <pubDate>Tue, 05 Aug 2025 14:56:00 +0000</pubDate>
      <link>https://dev.to/cheulong/basic-linux-command-sed-4kgo</link>
      <guid>https://dev.to/cheulong/basic-linux-command-sed-4kgo</guid>
      <description>&lt;p&gt;&lt;strong&gt;sed&lt;/strong&gt; command is stream editor for filtering and transforming text.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cheulong@master-node:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;hawaii-pizza.txt
Ingredients:
1. Flour
2. Mozzarella
3. Cheese
4. Ham
5. Pizza sauce
6. Pineapple Pineapple
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let replace pineapple from the list&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;# file doesn't change, only output&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/Pineapple/Feta/'&lt;/span&gt; hawaii-pizza.txt

&lt;span class="nt"&gt;---&lt;/span&gt;
Ingredients:
1. Flour
2. Mozzarella
3. Cheese
4. Ham
5. Pizza sauce
6. Feta Pineapple
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, the sed command replaces the first occurrence of the pattern in each line&lt;/p&gt;

&lt;h3&gt;
  
  
  Replace
&lt;/h3&gt;

&lt;p&gt;To replacing the nth occurrence of a pattern in a Line&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;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/Pineapple/Feta/2'&lt;/span&gt; hawaii-pizza.txt

&lt;span class="c"&gt;# All occurrence &lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/Pineapple/Feta/g'&lt;/span&gt; hawaii-pizza.txt
&lt;span class="c"&gt;# second occurrence to all occurrence &lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/Pineapple/Feta/2g'&lt;/span&gt; hawaii-pizza.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change in-place&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;# replace string&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/Pineapple/Feta/'&lt;/span&gt; hawaii-pizza.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replacing String on a Specific Line Number&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;sed&lt;/span&gt; &lt;span class="s1"&gt;'3 s/./!/'&lt;/span&gt; hawaii-pizza.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Printing Only the Replaced Lines&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;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'s/Pineapple/Feta/'&lt;/span&gt; hawaii-pizza.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replacing String on a Range of Lines&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;sed&lt;/span&gt; &lt;span class="s1"&gt;'1,3 s/./!/'&lt;/span&gt; hawaii-pizza.txt
&lt;span class="c"&gt;# from line 2 to last&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'2,$ s/./!/'&lt;/span&gt; hawaii-pizza.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Delete
&lt;/h3&gt;

&lt;p&gt;Remove something&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;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/Feta//'&lt;/span&gt; hawaii-pizza.txt
&lt;span class="nt"&gt;---&lt;/span&gt;
Ingredients:
1. Flour
2. Mozzarella
3. Cheese
4. Ham
5. Pizza sauce
6. 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete Line&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;sed&lt;/span&gt; &lt;span class="s1"&gt;'2d'&lt;/span&gt; hawaii-pizza.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete Last Line&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;sed&lt;/span&gt; &lt;span class="s1"&gt;'$d'&lt;/span&gt; hawaii-pizza.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To Delete line from range x to y&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;sed&lt;/span&gt; &lt;span class="s1"&gt;'4,$d'&lt;/span&gt; hawaii-pizza.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Insert
&lt;/h3&gt;

&lt;p&gt;Insert Text&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;sed&lt;/span&gt; &lt;span class="s1"&gt;'3i\Break'&lt;/span&gt; hawaii-pizza.txt  &lt;span class="c"&gt;# Insert text before line 3&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'3a\Break'&lt;/span&gt; hawaii-pizza.txt  &lt;span class="c"&gt;# Insert text after line 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Best Practices
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Be sure to back up your files before applying for the changes. (especially while using -i)&lt;/li&gt;
&lt;li&gt;Be careful before using regular expressions to avoid any unintended change.&lt;/li&gt;
&lt;li&gt;Always test your SED command on sample file first before using it to avoid unintentional change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Leave a comment if you have any questions.&lt;/p&gt;

&lt;p&gt;===========&lt;br&gt;
&lt;strong&gt;Please keep in touch&lt;/strong&gt;&lt;br&gt;
  &lt;a href="//cheulongsear.dev"&gt;Portfolio&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.linkedin.com/in/cheulongsear/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://github.com/cheulong" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.youtube.com/@allo-devops" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Basic Linux command (nano)</title>
      <dc:creator>Cheulong Sear</dc:creator>
      <pubDate>Mon, 04 Aug 2025 10:43:00 +0000</pubDate>
      <link>https://dev.to/cheulong/basic-linux-command-nano-3bel</link>
      <guid>https://dev.to/cheulong/basic-linux-command-nano-3bel</guid>
      <description>&lt;p&gt;&lt;strong&gt;nano&lt;/strong&gt; is a small and friendly editor. It copies the look and feel of Pico, but is free software, and implements several features that Pico lacks, such as opening multiple files, scrolling per line, undo/redo, syntax coloring, line numbering, and soft-wrapping overlong lines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;nano&lt;/strong&gt; command may not be pre-installed on all Linux distributions. To install it use&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;# Debian/Ubuntu&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;nano
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To use nano, just type &lt;code&gt;nano&lt;/code&gt;&lt;/p&gt;

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




&lt;p&gt;ctl + o: to save&lt;br&gt;
ctl + x: to exit&lt;/p&gt;

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




&lt;p&gt;ctl + : to replace&lt;/p&gt;

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




&lt;p&gt;ctl + j: to justify text&lt;br&gt;
ctl + c: to show the location of the cursor&lt;/p&gt;

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




&lt;p&gt;alt + a: set mark&lt;br&gt;
alt + 6: to copy&lt;/p&gt;

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




&lt;p&gt;ctl + k: to cut&lt;br&gt;
ctl + u: to paste&lt;br&gt;
alt + u: to undo&lt;br&gt;
alt + e: to redo&lt;/p&gt;

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




&lt;p&gt;ctl + d: to delete a character after cursor&lt;/p&gt;

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




&lt;p&gt;nano +20 pizza.txt: open file with nano&lt;/p&gt;

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




&lt;p&gt;nano -v pizza.txt: view only mode&lt;/p&gt;

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

&lt;h4&gt;
  
  
  Other useful command
&lt;/h4&gt;

&lt;p&gt;ctl + r: to read file&lt;br&gt;
ctl + w: to find&lt;br&gt;
ctl + /: to go to line&lt;/p&gt;

&lt;p&gt;Leave a comment if you have any questions.&lt;/p&gt;

&lt;p&gt;===========&lt;br&gt;
&lt;strong&gt;Please keep in touch&lt;/strong&gt;&lt;br&gt;
  &lt;a href="//cheulongsear.dev"&gt;Portfolio&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.linkedin.com/in/cheulongsear/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://github.com/cheulong" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.youtube.com/@allo-devops" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ubuntu</category>
      <category>linux</category>
      <category>cli</category>
      <category>homelab</category>
    </item>
    <item>
      <title>Basic Linux command (tree)</title>
      <dc:creator>Cheulong Sear</dc:creator>
      <pubDate>Sat, 02 Aug 2025 00:19:00 +0000</pubDate>
      <link>https://dev.to/cheulong/basic-linux-command-tree-jfm</link>
      <guid>https://dev.to/cheulong/basic-linux-command-tree-jfm</guid>
      <description>&lt;p&gt;&lt;strong&gt;tree&lt;/strong&gt; command a utility used to display the contents of directories in a tree-like, hierarchical format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tree&lt;/strong&gt; command may not be pre-installed on all Linux distributions. To install it use&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;# Debian/Ubuntu&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;tree
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Display current directory&lt;/span&gt;
tree

&lt;span class="c"&gt;# Display a specific directory&lt;/span&gt;
tree /path/to/directory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Use &lt;code&gt;-d&lt;/code&gt; to show only directory&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cheulong@master-node:~/parent&lt;span class="nv"&gt;$ &lt;/span&gt;tree &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;span class="nb"&gt;.&lt;/span&gt;
├── child1
├── child2
├── child3
├── child4
└── child5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;-f&lt;/code&gt; to show full path prefix for each file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cheulong@master-node:~/parent&lt;span class="nv"&gt;$ &lt;/span&gt;tree &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;span class="nb"&gt;.&lt;/span&gt;
├── ./child1
│   └── ./child1/text.txt
├── ./child2
├── ./child3
├── ./child4
└── ./child5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;-p&lt;/code&gt; to show permission for each file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cheulong@master-node:~/parent&lt;span class="nv"&gt;$ &lt;/span&gt;tree &lt;span class="nt"&gt;-p&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;drwxrwxr-x]  &lt;span class="nb"&gt;.&lt;/span&gt;
├── &lt;span class="o"&gt;[&lt;/span&gt;drwxrwxr-x]  child1
│   └── &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-rw-rw-r--&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;  text.txt
├── &lt;span class="o"&gt;[&lt;/span&gt;drwxrwxr-x]  child2
├── &lt;span class="o"&gt;[&lt;/span&gt;drwxrwxr-x]  child3
├── &lt;span class="o"&gt;[&lt;/span&gt;drwxrwxr-x]  child4
├── &lt;span class="o"&gt;[&lt;/span&gt;drwxrwxr-x]  child5
└── &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-rw-rw-r--&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;  file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;-L &amp;lt;level&lt;/code&gt; to limit the display depth to a specified level.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cheulong@master-node:~/parent&lt;span class="nv"&gt;$ &lt;/span&gt;tree &lt;span class="nt"&gt;-L&lt;/span&gt; 1
&lt;span class="nb"&gt;.&lt;/span&gt;
├── child1
├── child2
├── child3
├── child4
├── child5
└── file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;-sh&lt;/code&gt; to show size for each file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cheulong@master-node:~/parent&lt;span class="nv"&gt;$ &lt;/span&gt;tree &lt;span class="nt"&gt;-sh&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;4.0K]  &lt;span class="nb"&gt;.&lt;/span&gt;
├── &lt;span class="o"&gt;[&lt;/span&gt;4.0K]  child1
│   └── &lt;span class="o"&gt;[&lt;/span&gt;   0]  text.txt
├── &lt;span class="o"&gt;[&lt;/span&gt;4.0K]  child2
├── &lt;span class="o"&gt;[&lt;/span&gt;4.0K]  child3
├── &lt;span class="o"&gt;[&lt;/span&gt;4.0K]  child4
├── &lt;span class="o"&gt;[&lt;/span&gt;4.0K]  child5
└── &lt;span class="o"&gt;[&lt;/span&gt; 225]  file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Other Option
&lt;/h4&gt;

&lt;p&gt;-a: Include hidden files and directories (those starting with a dot).&lt;br&gt;
-F: Append a forward slash (/) to directory names and an asterisk (*) to executable files.&lt;br&gt;
--prune: Exclude empty directories from the output.&lt;br&gt;
-u: Print the username (or UID) of each file.&lt;br&gt;
-g: Print the group name (or GID) of each file.&lt;/p&gt;

&lt;p&gt;Leave a comment if you have any questions.&lt;/p&gt;

&lt;p&gt;===========&lt;br&gt;
&lt;strong&gt;Please keep in touch&lt;/strong&gt;&lt;br&gt;
  &lt;a href="//cheulongsear.dev"&gt;Portfolio&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.linkedin.com/in/cheulongsear/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://github.com/cheulong" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://www.youtube.com/@allo-devops" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ubuntu</category>
      <category>homelab</category>
      <category>linux</category>
      <category>cli</category>
    </item>
  </channel>
</rss>
