<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Prince Agrawal</title>
    <description>The latest articles on Forem by Prince Agrawal (@prkagrawal).</description>
    <link>https://forem.com/prkagrawal</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%2F454172%2Fe62b43e0-555c-4e09-9ef5-5bb807ca6916.jpeg</url>
      <title>Forem: Prince Agrawal</title>
      <link>https://forem.com/prkagrawal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/prkagrawal"/>
    <language>en</language>
    <item>
      <title>CI/CD to automate deployments to Kubernetes on DigitalOcean using Github Actions</title>
      <dc:creator>Prince Agrawal</dc:creator>
      <pubDate>Sat, 18 May 2024 22:40:16 +0000</pubDate>
      <link>https://forem.com/prkagrawal/cicd-to-automate-deployments-to-kubernetes-on-digitalocean-using-github-actions-4592</link>
      <guid>https://forem.com/prkagrawal/cicd-to-automate-deployments-to-kubernetes-on-digitalocean-using-github-actions-4592</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;In today's fast-paced development environment, having a CI/CD pipeline set up to automatically run tests, check project builds, and manage deployments can save significant time and enhance scalability. It has become a necessity for any well-maintained project. In this article, you will set up a comprehensive CI/CD pipeline for a Node.js typescript application, ultimately deploying it to a Kubernetes cluster on DigitalOcean. &lt;/p&gt;

&lt;p&gt;Here's what we'll cover:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a basic api in nodejs&lt;/li&gt;
&lt;li&gt;Converting to a typescript app&lt;/li&gt;
&lt;li&gt;Dockerizing the API&lt;/li&gt;
&lt;li&gt;Push the code to github&lt;/li&gt;
&lt;li&gt;CI/CD setup with github actions&lt;/li&gt;
&lt;li&gt;Kubernetes cluster creation on DigitalOcean&lt;/li&gt;
&lt;li&gt;Setting up access control for our cluster&lt;/li&gt;
&lt;li&gt;Creating kubernetes Deployment and Service&lt;/li&gt;
&lt;li&gt;Final changes to github action for automatic deployment to the kubernetes cluster&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://github.com/prkagrawal/nodejs-kubernetes-do-cicd" rel="noopener noreferrer"&gt;Github Project Repository&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Side note: There will be a second part of this article.
&lt;/h4&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;In order to follow along, you will need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://www.digitalocean.com" rel="noopener noreferrer"&gt;Digitalocean account&lt;/a&gt; to create Kubernetes cluster.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/" rel="noopener noreferrer"&gt;Github account&lt;/a&gt; for code hosting and running actions.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Docker installed&lt;/a&gt; on the system, also create an account on &lt;a href="https://hub.docker.com" rel="noopener noreferrer"&gt;DockerHub&lt;/a&gt;, we will use this to store our Docker images.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://kubernetes.io/docs/tasks/tools/" rel="noopener noreferrer"&gt;kubectl&lt;/a&gt; the kubernetes command line tool for controlling kubernetes cluster&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  1. Basic API in nodejs
&lt;/h3&gt;

&lt;p&gt;So first of all we need an application. Let's create a very basic server in Nodejs.&lt;/p&gt;

&lt;p&gt;create a new directory&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;node-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;go inside that directory&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;cd &lt;/span&gt;node-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize a npm project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install express, it is a node.js framework for api development&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;create an app.js file at the root directory of the project and insert the following in it&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Api is running...&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;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4000&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 ready at http://localhost:4000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is just creating a simple express server and after running &lt;code&gt;node app.js&lt;/code&gt; in the terminal if we go to &lt;a href="http://localhost:4000" rel="noopener noreferrer"&gt;http://localhost:4000&lt;/a&gt; you can verify it is live.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Converting to a typescript app
&lt;/h3&gt;

&lt;p&gt;Let's convert this into typescript now.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;change the extension of app.js to app.ts&lt;/li&gt;
&lt;li&gt;Install
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-D&lt;/span&gt; typescript @types/express @types/node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are installing these as dev dependencies so we use -D&lt;/p&gt;

&lt;p&gt;Next, generate &lt;code&gt;tsconfig.json&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx tsc &lt;span class="nt"&gt;--init&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may notice some errors appeared in the app.ts file, this is because of not specifying proper types, let's fix these. Change the app.ts file to the following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;Request&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="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="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Express&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&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;Request&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="nx"&gt;Response&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Api is running...&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;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4000&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 ready at http://localhost:4000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implicit type error would be now gone, but there is still a warning about &lt;code&gt;'req' being declared but its value is never read&lt;/code&gt;, we can ignore that for now.&lt;/p&gt;

&lt;p&gt;Let's change the built directory in the tsconfig file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"outDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./built"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's try to run the app again, we use ts-node-dev&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./node_modules/.bin/ts-node-dev app.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get &lt;code&gt;Server is ready at http://localhost:4000&lt;/code&gt; in the terminal.&lt;/p&gt;

&lt;p&gt;Let's also build the app, this is what happens in a production environment&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./node_modules/typescript/bin/tsc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This compiles the entire project typescript to javascript. A new directory called &lt;code&gt;built&lt;/code&gt; is created, as we specified above in the &lt;code&gt;tsconfig&lt;/code&gt; file. In that, you will find an app.js file. That you run through&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node ./built/app.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again go to localhost:4000, the API should be running&lt;/p&gt;

&lt;p&gt;Now let's set up some scripts so we don't have to run these packages through the node_modules directory.&lt;br&gt;
In the package.json, add the following scripts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ts-node-dev --poll ./app.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run build &amp;amp;&amp;amp; node built/app.js"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We added three scripts, one for running the project in development mode, one for building, and then a start script for both building and running the compiled JS code. The --poll flag in ts-node-dev constantly watches for file changes and ensures automatic server restarts, making it particularly advantageous in a containerized environment.&lt;/p&gt;

&lt;p&gt;Now you can just run the scripts using &lt;code&gt;npm run SCRIPT_NAME&lt;/code&gt;, as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit &lt;a href="http://localhost:4000" rel="noopener noreferrer"&gt;http://localhost:4000&lt;/a&gt; to ensure api is running.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Dockerizing the API
&lt;/h3&gt;

&lt;p&gt;Simple, just create a file named &lt;code&gt;Dockerfile&lt;/code&gt; (make sure the name is exactly this) in the root directory and add the following to it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20.13.1-alpine3.18&lt;/span&gt;

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

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# &lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 4000&lt;/span&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;FROM node:20.13.1-alpine3.18&lt;/code&gt;: Specifies the base image for the Docker container, which is Node.js version 20.13.1 on the Alpine 3.18 Linux distribution.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;WORKDIR /app&lt;/code&gt;: Sets the working directory inside the container to &lt;code&gt;/app&lt;/code&gt;. All subsequent commands will be run from this directory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;COPY package*.json ./&lt;/code&gt;: Copies &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt; from the host machine to the &lt;code&gt;/app&lt;/code&gt; directory in the container.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;RUN npm install&lt;/code&gt;: Installs the dependencies listed in &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;COPY . .&lt;/code&gt;: Copies all files and directories from the host machine’s current directory to the &lt;code&gt;/app&lt;/code&gt; directory in the container.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;RUN npm run build&lt;/code&gt;: Runs the build script defined in &lt;code&gt;package.json&lt;/code&gt;, typically used to compile or bundle the application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;EXPOSE 4000&lt;/code&gt;: Informs Docker that the container will listen on port 4000 at runtime.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;CMD [ "node", "./built/app.js" ]&lt;/code&gt;: Specifies the command to run the application when the container starts. Here, it runs Node.js to execute &lt;code&gt;./built/app.js&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because we do not want to copy unnecessary files into our production container, we will create a &lt;code&gt;.dockerignore&lt;/code&gt; file and add the following:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Now let's build the docker image, First, make sure the docker daemon is running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;now run the following command in the terminal to build the docker image, also replace prkagrawal with your username from DockerHub, this will be useful for pushing the image to DockerHub&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; prkagrawal/node-api &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;sudo&lt;/strong&gt;: Runs the command with superuser (root) privileges. (Docker daemon always runs as root user, so we need to run docker commands from root user otherwise do some configurations to run it without sudo preface)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;docker build&lt;/strong&gt;: Instructs Docker to build a new image from the Dockerfile in the current directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;-t prkagrawal/node-api&lt;/strong&gt;: Tags the image with the name &lt;code&gt;prkagrawal/node-api&lt;/code&gt;. The &lt;code&gt;-t&lt;/code&gt; flag is used to name and optionally tag the image in the &lt;code&gt;name:tag&lt;/code&gt; format. Here, the name is &lt;code&gt;prkagrawal/node-api&lt;/code&gt;, and if no specific tag is provided, it defaults to &lt;code&gt;latest&lt;/code&gt;, which is the case here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.&lt;/code&gt;&lt;/strong&gt; Specifies the build context, which is the current directory (&lt;code&gt;.&lt;/code&gt;). Docker uses the files in this directory to build the image.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the terminal, you should see, that the 12-digit ID will be different&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Successfully built 4a3270cc3c16
Successfully tagged prkagrawal/node-api:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run the following command to check docker images it should show up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker images
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's run this image using the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 4000:4000 prkagrawal/node-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and if you go to &lt;code&gt;http://localhost:4000&lt;/code&gt; you should see the message &lt;code&gt;Api is running...&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now if the process does not exit using Ctrl+C then use this, in another terminal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker ps &lt;span class="c"&gt;# get the id of the running container&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker stop &amp;lt;CONTAINER ID&amp;gt; &lt;span class="c"&gt;# kill it (gracefully)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Multistage build
&lt;/h4&gt;

&lt;p&gt;Now let's reduce the size of the docker image using a multi-stage build, replace &lt;code&gt;Dockerfile&lt;/code&gt; contents with the following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stage 1: Build&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20.13.1-alpine3.18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# Stage 2: Run&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20.13.1-alpine3.18&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/built ./built&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 4000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [ "node", "./built/app.js" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;COPY --from=builder /app/built ./built&lt;/code&gt;: Copies only the compiled artifacts from the builder stage.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RUN npm install --only=production&lt;/code&gt;: Installs only production dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now if you run the build command &lt;code&gt;sudo docker build -t prkagrawal/node-api .&lt;/code&gt; and check the image size, it should be significantly smaller.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Multi-stage builds in Docker allow you to use multiple FROM statements in your Dockerfile, each defining a separate stage. This approach helps in separating the build environment from the runtime environment, which can significantly reduce the size of the final image. Although here we are using the same images in both.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In a normal build, everything required for building and running the application is included in a single image. This means that all build tools, dependencies, and artifacts are part of the final image, even if they are not needed at runtime.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a multi-stage build, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Isolate Build Dependencies: Use a larger base image with all the necessary build tools and dependencies for compiling the application.&lt;/li&gt;
&lt;li&gt;Copy Only Necessary Artifacts: After building the application, you copy only the essential files (like compiled binaries or production-ready code) into a new, minimal image that has only the runtime dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's run this image using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 4000:4000 prkagrawal/node-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and go to &lt;code&gt;http://localhost:4000&lt;/code&gt; in your browser, you should see the &lt;code&gt;Api is running...&lt;/code&gt; message&lt;/p&gt;

&lt;p&gt;Now close the docker process using the way described above and let's push this to dockerhub. Login to docker using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and insert your dockerhub &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt;. You should see the &lt;code&gt;Login Succeeded&lt;/code&gt; message in the terminal.&lt;br&gt;
Now just run the following command to push the image to dockerhub, it automatically creates a public repo and pushes the image to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker push prkagrawal/node-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Optional: If you want to push to a private repo, go to dockerhub, and create a private repo. Ideally, the repo and image names should be the same, otherwise, make sure to tag them properly with
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker tag &amp;lt;IMAGE_NAME&amp;gt; DOCKERHUB_USERNAME/DOCKERHUB_REPO_NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we have been using &lt;code&gt;node-api&lt;/code&gt; as IMAGE_NAME and replacing the username and repo names with their corresponding values. Then run the push command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker push DOCKERHUB_USERNAME/DOCKERHUB_REPO_NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Pushing the code to github
&lt;/h3&gt;

&lt;p&gt;In the root directory, in the terminal initialize a new git repo with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need a .gitignore file, to avoid pushing node packages, build files, env vars(currently we don't have any), and various other files that do not need to go to the repo. So create a file named &lt;code&gt;.gitignore&lt;/code&gt; and paste the following in it:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;First, add all the files in the root directory to the git staging&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then commit these files to your repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"initial commit"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now go to github.com, and create a repository for this project, I created one called &lt;code&gt;nodejs-kubernetes-do-cicd&lt;/code&gt;, be sure to replace this with your GitHub repository name. Then&lt;br&gt;
go to your repo on GitHub, there should be instructions on how to &lt;code&gt;push an existing repository from the command line&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;They would be like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote add origin git@github.com:prkagrawal/nodejs-kubernetes-do-cicd.git
git branch &lt;span class="nt"&gt;-M&lt;/span&gt; main
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first command adds a remote called origin at the repo address, the second renames the current branch to main, and then the third one pushes the files to Github. If you don't have a GitHub SSH setup, you will be asked for a username and password for your GitHub account in this step.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. CI/CD setup with GitHub action
&lt;/h3&gt;

&lt;p&gt;Go to your GitHub repo and add the docker image action as shown below&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fgifs%2Fadding-docker-action.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fgifs%2Fadding-docker-action.gif" alt="Adding docker image action"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This also gives us a starting point to build upon. Now pull the latest changes to your local repo using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git pull
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the .github/workflows/deploy-to-kubernetes-on-digitalocean.yml file to the following:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-to-kubernetes-on-digitalocean&lt;/span&gt; &lt;span class="c1"&gt;# Name of the GitHub Actions workflow&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Trigger the workflow on push events to the main branch&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Trigger the workflow on pull requests targeting the main branch&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;IMAGE_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prkagrawal/node-api&lt;/span&gt; &lt;span class="c1"&gt;# image name&lt;/span&gt;
  &lt;span class="na"&gt;IMAGE_TAG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.sha }}&lt;/span&gt; &lt;span class="c1"&gt;# get the commit SHA from the GitHub context (useful for tagging the Docker image because it's unique)&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Define a job named 'build'&lt;/span&gt;

    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt; &lt;span class="c1"&gt;# Specify the runner to use for the job, here it's the latest version of Ubuntu&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt; &lt;span class="c1"&gt;# Step to check out the repository code using the checkout action&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;Build the Docker image&lt;/span&gt; &lt;span class="c1"&gt;# Step name&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker build -t "$IMAGE_NAME:$IMAGE_TAG" .&lt;/span&gt; &lt;span class="c1"&gt;# build the Docker image using envs defined above&lt;/span&gt;

    &lt;span class="c1"&gt;# login to dockerhub then push the image to the dockerhub repo&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;Push Docker image&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
        &lt;span class="s"&gt;echo ${{secrets.DOCKERHUB_PASS}} | docker login -u ${{secrets.DOCKERHUB_USERNAME}} --password-stdin&lt;/span&gt;
        &lt;span class="s"&gt;docker push "$IMAGE_NAME:$IMAGE_TAG"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure the indentation is correct, otherwise, you will get an error, yml is strict about it. Other than that it is an easy-to-use and human-readable format. &lt;/p&gt;

&lt;p&gt;So, I have added some comments for what each step does in the file and also renamed it to &lt;code&gt;deploy-to-kubernetes-on-digitalocean.yml&lt;/code&gt;. The filename doesn't matter, you can keep it whatever you want. &lt;/p&gt;

&lt;p&gt;Also defined are some environment variables at the top, IMAGE_NAME so we can reuse it, and IMAGE_TAG to tag the docker image to the latest commit sha. The tagging is important because it is used to uniquely identify an image (combination of filesystem layers). When a user pulls an image they know which version they are pulling from the tag. So, if the tag is the same in the kubernetes deployment it will not pull again even when the policy is set to pull always, because according to it, that version of the image is already there. So, we update the tag to update the version of the image.&lt;/p&gt;

&lt;p&gt;Now we are going to use these envs to build our docker image, using the same old build command. Finally added one more step to login to dockerhub and then push the image to dockerhub repo.&lt;/p&gt;

&lt;p&gt;Although we are using echo on the password, GitHub redacts the secrets from getting logged and then its value is piped to the docker login command which reads it as an input.&lt;/p&gt;

&lt;p&gt;Now before we push this to see our action in action, we need to add DOCKERHUB_USERNAME and DOCKERHUB_PASS to GitHub action secrets. Use the following process and replace the values with your own:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fgifs%2Fadding-secrets-for-github-action.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fgifs%2Fadding-secrets-for-github-action.gif" alt="Adding DOCKERHUB secrets in github repo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sidenote: my dockerhub password is just a dummy one, so don't try to use it that won't work, use your password&lt;/p&gt;

&lt;p&gt;Now, go ahead and commit the changes and push the updates to Gihtub, and see our action in action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"updated workflow action to deploy docker image to dockerhub"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the action run completes, you can go to dockerhub and should be able to see the latest commit-sha tagged image.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Kubernetes Cluster creation on digitalocean
&lt;/h3&gt;

&lt;p&gt;Now let's create a kubernetes cluster on digitalocean if you don't already have one. Go to &lt;a href="https://cloud.digitalocean.com/kubernetes/clusters" rel="noopener noreferrer"&gt;kubernetes Page&lt;/a&gt; after logging in to your account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fgifs%2Fcreate-kubernetes-cluster-on-digitalocean.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fgifs%2Fcreate-kubernetes-cluster-on-digitalocean.gif" alt="creating digital ocean cluster"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;choose a data center nearest to you, and then you have to build a node pool. On digitalocean a node is a droplet. I am just selecting 1 node with the lowest config since that will be enough in this case. Choose a name for the cluster, or you can leave it as it is and then there is an option to add tags for different environments but for now, I kept it empty.&lt;/p&gt;

&lt;p&gt;Then click the create cluster button, it will take some time to get created. After that, there are two ways to connect to the cluster - Automated and manual. Automated uses doctl which is digital ocean cli, you will have to download it first. Here I am going to be using the Manual method. Go to the manual tab and download the cluster configuration file.&lt;/p&gt;

&lt;p&gt;Now you can run a command on a cluster like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt; ~/Downloads/node-api-kubeconfig.yaml get pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are using kubectl for running the command and passing its path to the config file through &lt;code&gt;--kubeconfig&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;You should see an output like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Fverify-connection-to-k8s-cluster.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Fverify-connection-to-k8s-cluster.png" alt="Verify connection to k8s cluster"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means we were able to connect to the cluster and no resources were found because the cluster was just created and there are no pods on it.&lt;/p&gt;

&lt;p&gt;You can also run the following command to get all the resources on the cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt; ~/Downloads/node-api-kubeconfig.yaml get all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Fget-all-resources.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Fget-all-resources.png" alt="Get all resources"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the default way to run commands with kubectl by passing &lt;code&gt;--kubeconfig&lt;/code&gt; flag and path to the config file on all commands. But if you are not running other kubernetes clusters then you can copy the kubeconfig file to a folder on your home directory called .kube (you can make that in case it does not exist). Then just rename it to &lt;code&gt;config&lt;/code&gt;. Then you can just run the commands as&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;and should see the same message as above. Here we are going to be passing the &lt;code&gt;--kubeconfig&lt;/code&gt; flag on all commands, just remove that part if you moved and renamed the config file.&lt;/p&gt;

&lt;p&gt;Also, we are going to be using the default namespace for all operations. Namespaces are a way to isolate groups of resources within a cluster. Mainly intended for use in environments with multiple users and projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Setting up access control for our cluster
&lt;/h3&gt;

&lt;p&gt;Anyway, now we need to set up access control on kubernetes, using access control we can manage which applications and users are either allowed or denied certain access or permissions. Till now we have been using the default admin user to authenticate to our cluster. If this gets compromised our whole cluster will be compromised so instead we are going to use RBAC (Role-based Access Control) with a service account with specific roles.&lt;/p&gt;

&lt;p&gt;We start with creating a cluster user(Service Account), then create a Role in which we specify which permissions it has on the cluster. Finally, a Role Binding is used to link a Service Account to a Role.&lt;/p&gt;

&lt;p&gt;Let's get started, and create a file called &lt;code&gt;api-service-account.yaml&lt;/code&gt; in a separate folder on your system, this directory can be used for keeping kubernetes related files, configs, etc, and input the following in it&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;api-service-account&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All kubernetes configs are in yaml format and kubernetes resources are defined by their "kind." Each kind represents a specific type of resource within the Kubernetes API. In this, it is a service account. The metadata field is used to add more info, here we are giving this &lt;code&gt;ServiceAccount&lt;/code&gt; the name &lt;code&gt;api-service-account&lt;/code&gt; and using the default namespace.&lt;/p&gt;

&lt;p&gt;Apply this using kubectl, don't forget to replace the config path or remove the flag altogether:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt; ~/Downloads/node-api-kubeconfig.yaml apply &lt;span class="nt"&gt;-f&lt;/span&gt; ~/kube-general/api-service-account.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see an output like this:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Fservice-account-created.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Fservice-account-created.png" alt="Service Account Created"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;kubectl apply -f&lt;/code&gt; command is used in Kubernetes to create or update resources defined in a configuration file.&lt;/p&gt;

&lt;p&gt;Next, create a Role that specifies the permissions for the Service Account. Roles are namespace-scoped, so they grant permissions within a specific namespace. Create a file named &lt;code&gt;api-role.yaml&lt;/code&gt;&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;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Role&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;api-role&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apps"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;batch"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;extensions"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;networking.k8s.io"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deployments"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;services"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replicasets"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pods"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jobs"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cronjobs"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ingresses"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Then apply it using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt; ~/Downloads/node-api-kubeconfig.yaml apply &lt;span class="nt"&gt;-f&lt;/span&gt; ~/kube-general/api-role.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output will be like:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Frole-created.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Frole-created.png" alt="Role created"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, create role binding to link the service account and role, create a file named &lt;code&gt;api-role-binding.yaml&lt;/code&gt;&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;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RoleBinding&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;api-role-binding&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;subjects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&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;api-service-account&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;roleRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Role&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;api-role&lt;/span&gt;
  &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt; ~/Downloads/node-api-kubeconfig.yaml apply &lt;span class="nt"&gt;-f&lt;/span&gt; ~/kube-general/api-role-binding.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you will see&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Frole-binding-created.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Frole-binding-created.png" alt="Role Binding created"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now if you run the command to get the service accounts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt; ~/Downloads/node-api-kubeconfig.yaml get sa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Fget-sa-using-kubectl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Fget-sa-using-kubectl.png" alt="get service account using kubectl"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;you can see we do have our created account but no secret associated with it, that we can use to authenticate. Let's create one, create one more file named &lt;code&gt;api-secret.yaml&lt;/code&gt;, and insert the following in it:&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes.io/service-account-token&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;api-secret&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kubernetes.io/service-account.name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api-service-account"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt; ~/Downloads/node-api-kubeconfig.yaml apply &lt;span class="nt"&gt;-f&lt;/span&gt; ~/kube-general/api-secret.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the secret-created output&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Fsecret-created.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Fsecret-created.png" alt="Secret created"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now if we describe the Secret, we can see that a token was generated for it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt; ~/Downloads/node-api-kubeconfig.yaml describe secret api-secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are fields like name, namespace, labels, annotations, and type also there is a token and ca.crt these two will be useful for connecting to our cluster without kubeconfig, now let's verify we can connect to our cluster using this token using the following command, replace &lt;code&gt;server-url-from-config&lt;/code&gt; with the server url from the kubeconfig file that you downloaded and &lt;code&gt;token-value&lt;/code&gt; with your token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--insecure-skip-tls-verify&lt;/span&gt; &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/dev/null"&lt;/span&gt; &lt;span class="nt"&gt;--server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;server-url-from-config &lt;span class="nt"&gt;--token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;token-value get pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using --kubeconfig="/dev/null" ensures that kubectl ignores your existing config file and credentials, instead relying solely on the provided token, also notice we are adding flag &lt;code&gt;--insecure-skip-tls-verify&lt;/code&gt;, this flag in kubectl is used to bypass the certificate validation step when making HTTPS requests to the Kubernetes API server. This means that kubectl will not check if the server's certificate is signed by a trusted Certificate Authority (CA), which can be useful in certain scenarios, such as testing and development environments.&lt;/p&gt;

&lt;p&gt;To not have to use this flag we will have to pass the ca.crt (certified authority certificate) flag to kubectl commands, which we will set up in our github workflow. The certificate can be found in the kubeconfig file, it's the &lt;code&gt;certificate-authority-data&lt;/code&gt; field.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Creating kubernetes Deployment and Service
&lt;/h3&gt;

&lt;h4&gt;
  
  
  These are some common kubernetes terms:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Cluster: Imagine your entire Kubernetes setup as a cluster, a collection of nodes working together. This is the foundation of your infrastructure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Nodes: Each node is a worker machine (virtual or physical) in your cluster. Nodes run your applications through pods. For example, a node could be a virtual machine in the cloud.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pods: The smallest deployable units in Kubernetes, representing an instance of your application. For instance, a pod might run a web server. Pods can have one or more containers that share storage and network.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deployments: Manage and maintain your pods, ensuring a specified number is always running. If a pod fails, the Deployment replaces it. Think of it as an automated way to keep your web server instances running.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Services: Provide stable network access to pods, offering a single IP address and DNS name to access them. Services also load balance traffic across the pods. For example, users access your web server through a Service.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ingress: Manages external access to Services, typically via HTTP. It routes incoming traffic to the correct Service based on the request's URL. For instance, my-app.example.com might be routed to your web server Service.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ConfigMaps and Secrets: Store configuration data and sensitive information (like passwords), respectively. They decouple environment-specific configurations from your container images, making it easier to manage configurations. For example, your web server might read its database connection string from a ConfigMap and its password from a Secret.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So now let's create Deployment, start with creating a directory called &lt;code&gt;k8s&lt;/code&gt; in the root of the project, inside it, we will store all the kubernetes related stuff. Inside the &lt;code&gt;k8s&lt;/code&gt; directory create a file named &lt;code&gt;deployment.yaml&lt;/code&gt; and paste the following in it:&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;node-api&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node-api&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node-api&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node-api&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&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;container-name&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prkagrawal/node-api:latest&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are 2 main parts here, &lt;code&gt;metadata&lt;/code&gt; and &lt;code&gt;spec&lt;/code&gt;. Let's start with the spec, the &lt;code&gt;template&lt;/code&gt; field in &lt;code&gt;spec&lt;/code&gt; defines the blueprint for pods, it is the configuration of pods within the configuration of deployment. It has its metadata and spec and in the specification of pods, we have the definition of &lt;code&gt;containers&lt;/code&gt; with the same fieldname. A pod can have one or more containers but mostly one main application per pod. Here we define which image will be used to create the pod, in our case it will be the pushed image from DockerHub. The &lt;code&gt;containerPort&lt;/code&gt; is the exposed port from the Dockerfile on which our container will listen.&lt;/p&gt;

&lt;p&gt;Now we also have 2 &lt;code&gt;labels&lt;/code&gt; and one &lt;code&gt;matchLabel&lt;/code&gt;, in kubernetes, we can give any component a label. Labels are &lt;code&gt;key/value&lt;/code&gt; pair attached to kubernetes resources, they act as identifiers but are not unique, here all the pods created using in deployment config will have the same label. This way we can identify all the pods of the same application using the label because names of the pods will be unique. So, for pods label is a required field while in &lt;code&gt;deployment&lt;/code&gt; it is optional but a good practice.&lt;/p&gt;

&lt;p&gt;Now how does kubernetes know which pods belong to which deployment, that is what the &lt;code&gt;selector&lt;/code&gt; in deployment &lt;code&gt;spec&lt;/code&gt; is for. So all the pods that match the &lt;code&gt;app: node-api&lt;/code&gt; label are in this deployment. You can select any &lt;code&gt;key: value&lt;/code&gt; pairs, it is just standard practice to use the &lt;code&gt;app&lt;/code&gt; key in labels.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;apiVersion&lt;/code&gt; field specifies the version of the Kubernetes API that you are using to create the object and the &lt;code&gt;kind&lt;/code&gt; field specifies the type of Kubernetes object you are defining, here that is &lt;code&gt;Deployment&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now let's create a service, and make a file named &lt;code&gt;service.yaml&lt;/code&gt; inside the k8s directory and paste the following in it:&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;service-name&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node-api&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodePort&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4000&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4000&lt;/span&gt;
      &lt;span class="na"&gt;nodePort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30200&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node-api&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the Service also has two main sections, metadata and spec, the &lt;code&gt;selector&lt;/code&gt; in &lt;code&gt;spec&lt;/code&gt; is used to define which pods belong to the service, so it should match the label of the pods. Service will then know it can forward requests to these pods.&lt;/p&gt;

&lt;p&gt;Service is accessible in the cluster using its IP address and port, in which the &lt;code&gt;port&lt;/code&gt; can be anything 80, 8080, 3000, etc. Now we have the &lt;code&gt;targetPort&lt;/code&gt; which is the port of the pods that belong to the service and it should be the same as the &lt;code&gt;containerPort&lt;/code&gt; because that's where the service should forward the request to.&lt;/p&gt;

&lt;p&gt;Now there are 2 types of services - internal and external. And we want to be able to access our app from a web browser, so we need an external service. That is what &lt;code&gt;type&lt;/code&gt; in &lt;code&gt;spec&lt;/code&gt; is used to define, by default it will be an internal service of type &lt;code&gt;ClusterIP&lt;/code&gt; if the type is not there. Here we define it as NodePort which is an external service type and it requires a third port which is called &lt;code&gt;nodePort&lt;/code&gt; in &lt;code&gt;ports&lt;/code&gt;. This is the port where our application will be accessible on the IP address of k8s nodes. So we will access the service at nodeip:nodePort which will then access the pods behind it.&lt;/p&gt;

&lt;p&gt;The range for nodePort is 30000-32767 in k8s, the nodePort can be any value inside that.&lt;/p&gt;

&lt;p&gt;With these configs, we can now create the corresponding resources in kubernetes. Let's do that, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt; ~/Downloads/node-api-kubeconfig.yaml apply &lt;span class="nt"&gt;-f&lt;/span&gt; k8s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the following output&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Fdeployment-and-service-created.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Fdeployment-and-service-created.png" alt="Deployment and Service created"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's check all the resources in our cluster&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt; ~/Downloads/node-api-kubeconfig.yaml get all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, let's verify if our api is running by forwarding one port of the local machine to the port that the service is exposing, copy the name of the pod which is the string after &lt;code&gt;pod/&lt;/code&gt; -&amp;gt; &lt;code&gt;node-api-568dc945d7-928qf&lt;/code&gt; something like this, and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt; ~/Downloads/node-api-kubeconfig.yaml port-forward node-api-568dc945d7-928qf 4000:4000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oops, we got an error, what went wrong, it is saying the pod is not running, why is it not running?&lt;/p&gt;

&lt;p&gt;A pod is the smallest deployable unit in k8s, just a layer over our application container. Where is our application container defined, in the &lt;code&gt;deployment.yaml&lt;/code&gt; file? Now it is using the deployed image from our dockerhub. And if you remember earlier we tagged our image with github commit sha, but in deployment, we used the &lt;code&gt;latest&lt;/code&gt; tag, which doesn't exist, so no image, no container, and nothing for the pod to run. Ok, for now, let's just go to the dockerhub repo for our image and get the tag from there then replace the latest in the deployment.yaml with it. The image field will look something like this&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="nn"&gt;...&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prkagrawal/node-api:5c6b2aec516f194b97af3eea02cdab3ed0aa498b&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After making the change, we again have to apply the k8s configs, run the command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt; ~/Downloads/node-api-kubeconfig.yaml apply &lt;span class="nt"&gt;-f&lt;/span&gt; k8s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;deployment.apps/node-api created&lt;br&gt;
service/service-name created&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Fupdate-after-tag-fix.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Fupdate-after-tag-fix.png" alt="Update after tag fix"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now once more get the pod name with &lt;code&gt;get pods&lt;/code&gt;, and run the command for port forwarding&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt; ~/Downloads/node-api-kubeconfig.yaml port-forward node-api-568dc945d7-928qf 4000:4000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see an output like this now&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Fport-forwarded.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Fport-forwarded.png" alt="Port forwarded"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you go to &lt;a href="http://localhost:4000" rel="noopener noreferrer"&gt;http://localhost:4000&lt;/a&gt;, you can see the &lt;code&gt;Api is running...&lt;/code&gt; message&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Fport-forwarded-verification.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Fport-forwarded-verification.png" alt="Port forwarded output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we don't want to manually do the tag replacement after every commit in a production environment, let's set up a script for that. Create a new directory in the root named &lt;code&gt;scripts&lt;/code&gt; and inside it, a file named &lt;code&gt;update-tag.sh&lt;/code&gt; and paste the following into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;COMMIT_SHA1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;

&lt;span class="c"&gt;# Define the desired commit SHA value&lt;/span&gt;
&lt;span class="nv"&gt;NEW_COMMIT_SHA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$COMMIT_SHA1&lt;/span&gt;

&lt;span class="c"&gt;# Export the commit SHA as an environment variable&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;COMMIT_SHA1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NEW_COMMIT_SHA&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Use envsubst to replace the placeholder in the Deployment YAML file&lt;/span&gt;
envsubst &lt;span class="s1"&gt;'$COMMIT_SHA1'&lt;/span&gt; &amp;lt; k8s/deployment.yaml &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; temp.yaml &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mv &lt;/span&gt;temp.yaml k8s/deployment.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We pass the sha1 to bash script as an argument, then store it in a new sha variable the export it to make it available to envsubst. Then update the original deployment.yaml file with the substitution applied, without creating a new file. It uses a temporary file (temp.yaml) to store the modified content and then renames it back to deployment.yaml after the update.&lt;/p&gt;

&lt;p&gt;Now make it executable&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;chmod&lt;/span&gt; +x scripts/update-tag.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One more thing, let's also update the tag value in our deployment.yaml file to the variable name &lt;code&gt;$COMMIT_SHA1&lt;/code&gt;, the image looks like this:&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;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prkagrawal/$COMMIT_SHA1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can verify the script is working by running it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./scripts/update-tag.sh tag-val
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;$COMMIT_SHA1&lt;/code&gt; in deployment.yaml should have been replaced. Now transform it back to the variable, we will add this functionality through the Github actions workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Final changes to github action for automatic deployment to the kubernetes cluster.
&lt;/h3&gt;

&lt;p&gt;Now go to the &lt;code&gt;.github/workflows/deploy-to-kubernetes-on-digitalocean.yml&lt;/code&gt; file and paste the following at the end after the previous step&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="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;Install envsubst&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
    &lt;span class="s"&gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get -y install gettext-base&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;Install kubectl&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
    &lt;span class="s"&gt;curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl&lt;/span&gt;
    &lt;span class="s"&gt;chmod u+x ./kubectl&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;Substitute variables in deployment.yaml by running script&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
    &lt;span class="s"&gt;./scripts/update-tag.sh "$IMAGE_TAG" ${{secrets.KUBERNETES_CLUSTER_CERTIFICATE}} ${{secrets.KUBERNETES_SERVER}} ${{secrets.KUBERNETES_TOKEN}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We added three new steps, one for installing &lt;code&gt;envsubst&lt;/code&gt; which is part of &lt;code&gt;gettext-base&lt;/code&gt; then one for installing &lt;code&gt;kubectl&lt;/code&gt; finally the last one for replacing the tag using commit sha-1 in &lt;code&gt;deployment.yaml&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Finally, let's also set up a script to deploy to kubernetes and it is as simple as applying the configs in the k8s directory to create deployment and service. For that we need to add three more variables to the github repo action secrets, they are &lt;code&gt;KUBERNETES_TOKEN&lt;/code&gt;, &lt;code&gt;KUBERNETES_SERVER&lt;/code&gt;, and &lt;code&gt;KUBERNETES_CLUSTER_CERTIFICATE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The value of &lt;code&gt;KUBERNETES_TOKEN&lt;/code&gt; will be the token we used earlier to authenticate the service account user, retrieve it by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt; ~/Downloads/node-api-kubeconfig.yaml describe secret api-secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From out copy the value of a token variable without any spaces at ends, then go Github repo and add this as an action secret. Follow the same steps we used earlier to add dockerhub secrets.&lt;/p&gt;

&lt;p&gt;The value of &lt;code&gt;KUBERNETES_SERVER&lt;/code&gt; is the --server flag that we passed earlier to verify connection to the cluster without kubeconfig, it can be found in the kubeconfig file you downloaded after creating the cluster on digitalocean. And &lt;code&gt;KUBERNETES_CLUSTER_CERTIFICATE&lt;/code&gt; is also available on this config file, it's the &lt;code&gt;certificate-authority-data&lt;/code&gt; field. It should be a long string, copy all of it. Then go to the github repo and add these as secrets as well.&lt;/p&gt;

&lt;p&gt;Now add this step to the &lt;code&gt;.github/workflows/deploy-to-kubernetes-on-digitalocean.yml&lt;/code&gt; file:&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="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;Deploy to Kubernetes&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
    &lt;span class="s"&gt;echo ${{secrets.KUBERNETES_CLUSTER_CERTIFICATE}} | base64 --decode &amp;gt; cert.crt&lt;/span&gt;
    &lt;span class="s"&gt;./kubectl \&lt;/span&gt;
      &lt;span class="s"&gt;--kubeconfig=/dev/null \&lt;/span&gt;
      &lt;span class="s"&gt;--server=${{secrets.KUBERNETES_SERVER}} \&lt;/span&gt;
      &lt;span class="s"&gt;--certificate-authority=cert.crt \&lt;/span&gt;
      &lt;span class="s"&gt;--token=${{secrets.KUBERNETES_TOKEN}} \&lt;/span&gt;
      &lt;span class="s"&gt;apply -f ./k8s/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your completed &lt;code&gt;deploy-to-kubernetes-on-digitalocean&lt;/code&gt; file will look like this:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-to-kubernetes-on-digitalocean&lt;/span&gt; &lt;span class="c1"&gt;# Name of the GitHub Actions workflow&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Trigger the workflow on push events to the main branch&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Trigger the workflow on pull requests targeting the main branch&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;IMAGE_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prkagrawal/node-api&lt;/span&gt; &lt;span class="c1"&gt;# image name&lt;/span&gt;
  &lt;span class="na"&gt;IMAGE_TAG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.sha }}&lt;/span&gt; &lt;span class="c1"&gt;# get the commit SHA from the GitHub context (useful for tagging the Docker image because it's unique)&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Define a job named 'build'&lt;/span&gt;

    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt; &lt;span class="c1"&gt;# Specify the runner to use for the job, here it's the latest version of Ubuntu&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt; &lt;span class="c1"&gt;# Step to check out the repository code using the checkout action&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;Build the Docker image&lt;/span&gt; &lt;span class="c1"&gt;# Step name&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker build -t "$IMAGE_NAME:$IMAGE_TAG" .&lt;/span&gt; &lt;span class="c1"&gt;# build the Docker image using envs defined above&lt;/span&gt;

    &lt;span class="c1"&gt;# login to dockerhub then push the image to the dockerhub repo&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;Push Docker image&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
        &lt;span class="s"&gt;echo ${{secrets.DOCKERHUB_PASS}} | docker login -u ${{secrets.DOCKERHUB_USERNAME}} --password-stdin&lt;/span&gt;
        &lt;span class="s"&gt;docker push "$IMAGE_NAME:$IMAGE_TAG"&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;Install envsubst&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
        &lt;span class="s"&gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get -y install gettext-base&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;Install kubectl&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
        &lt;span class="s"&gt;curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl&lt;/span&gt;
        &lt;span class="s"&gt;chmod u+x ./kubectl&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;Substitute variables in deployment.yaml by running script&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
        &lt;span class="s"&gt;./scripts/update-tag.sh "$IMAGE_TAG"&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;Deploy to Kubernetes&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
        &lt;span class="s"&gt;echo ${{secrets.KUBERNETES_CLUSTER_CERTIFICATE}} | base64 --decode &amp;gt; cert.crt&lt;/span&gt;
        &lt;span class="s"&gt;./kubectl \&lt;/span&gt;
          &lt;span class="s"&gt;--kubeconfig=/dev/null \&lt;/span&gt;
          &lt;span class="s"&gt;--server=${{secrets.KUBERNETES_SERVER}} \&lt;/span&gt;
          &lt;span class="s"&gt;--certificate-authority=cert.crt \&lt;/span&gt;
          &lt;span class="s"&gt;--token=${{secrets.KUBERNETES_TOKEN}} \&lt;/span&gt;
          &lt;span class="s"&gt;apply -f ./k8s/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have simply added a step to first decode the cluster certificate as it is in base64 then save it to cert.crt file then a step to apply configs in the k8s directory using token and certificate for verification.&lt;/p&gt;

&lt;p&gt;Now add all these changes then commit and push them to Github:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"deployment cicd setup"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moment of truth, we have finally deployed to kubernetes using github actions. Now let's check if we can access our api. Remember service exposes the node in the form of node-ip:nodePort, we can access that, and get our node details using the:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt; ~/Downloads/node-api-kubeconfig.yaml get nodes &lt;span class="nt"&gt;-o&lt;/span&gt; wide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-o wide&lt;/code&gt; flag is used to get additional details about a resource. Now copy the EXTERNAL-IP of the node, and &lt;code&gt;nodePort&lt;/code&gt; is the one we used earlier in our service.yaml file which is &lt;code&gt;30200&lt;/code&gt; then go to your web browser and paste it as &lt;code&gt;http://EXTERNAL-IP:nodePort&lt;/code&gt; which for me is &lt;code&gt;http://139.59.29.96:30200&lt;/code&gt;, you should see the &lt;code&gt;Api is running...&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Faccess-api-through-nodeip.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fprkagrawal%2Fnodejs-kubernetes-do-cicd%2Fmain%2Fstatic%2Fimages%2Faccess-api-through-nodeip.png" alt="Api running on nodeip:nodePort"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Now we have a fully functioning ci-cd pipeline in place, which on push or pull request to the main branch automatically builds a docker image, pushes it to dockerhub, and then deploys to the kubernetes cluster on digitalocean.&lt;/p&gt;

&lt;p&gt;Using NodePort, each service requires users to access different node IP addresses and ports. While this is fine for testing and development it is not user-friendly. In the next part of this article, we will set up ingress to provide a single point of access to multiple services within the cluster using a single external IP address and configure a domain name with it.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>githubactions</category>
      <category>cicd</category>
    </item>
  </channel>
</rss>
