<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Ranindu's Blog]]></title><description><![CDATA[Ranindu's blog is a place where anyone can start their journey into the world of DevOps. Helping with Docker, CI/CD, Monitoring, and IaC.]]></description><link>https://blog.ranindu.com</link><generator>RSS for Node</generator><lastBuildDate>Thu, 08 Oct 2026 04:23:12 GMT</lastBuildDate><atom:link href="https://blog.ranindu.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Learn IPv4 and Subnet Masking: The Beginner's Guide]]></title><description><![CDATA[“There are 10 types of people in this world, those who understand binary and those who don't.” ~ a wise sage

Understanding Binary
The computer and the human brain work differently, you and I know that. While humans think about numbers using the deci...]]></description><link>https://blog.ranindu.com/learn-ipv4-and-subnet-masking-the-beginners-guide</link><guid isPermaLink="true">https://blog.ranindu.com/learn-ipv4-and-subnet-masking-the-beginners-guide</guid><category><![CDATA[networking]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[subnetmask]]></category><category><![CDATA[basics]]></category><dc:creator><![CDATA[Ranindu Abeyratne]]></dc:creator><pubDate>Wed, 04 Dec 2024 06:12:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/ZLUi-sGUbz4/upload/b10c362a6c27805866ddc7c12016f2ee.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>“<em>There are 10 types of people in this world, those who understand binary and those who don't.” ~ a wise sage</em></p>
</blockquote>
<h2 id="heading-understanding-binary">Understanding Binary</h2>
<p>The computer and the human brain work differently, you and I know that. While humans think about numbers using the decimal system, the computers convert everything into binary to understand them. When talking about IPs, one of the first things that you have to understand is that each IP address is subdivided into four 8-bit parts known as octets.</p>
<p>Therefore when you see an IP such as</p>
<pre><code class="lang-plaintext">76.240.249.145
</code></pre>
<p>your computer will read it as</p>
<pre><code class="lang-plaintext">01001100.11110000.11111001.10010001
</code></pre>
<p>When it comes to the octet the <strong>minimum value</strong> you could have is 0 = 00000000 but as an IP cannot be 0.0.0.0 unless it’s a wildcard, somewhere within the four parts it would have to be 1 = 00000001.</p>
<p>The <strong>maximum value</strong> you could have in the octet would be 11111111 = 255. So, an IP with 255.0.1.255 = 11111111.00000000.00000001.11111111.</p>
<h2 id="heading-converting-decimal-to-binary-amp-vice-versa">Converting Decimal to Binary &amp; Vice Versa</h2>
<p>When converting decimal to binary what you need to know is that binary increases in the number of digits for each increment to the power of 2, after 0 &amp; 1 of course.</p>
<p>Eg:</p>
<p>1 = 1 + 4 = 5</p>
<ul>
<li><p>0 = 0</p>
</li>
<li><p>2 = 2^1 = 10</p>
</li>
<li><p>4 = 2^2 = 100</p>
</li>
<li><p>8 = 2^3 = 1000</p>
</li>
</ul>
<p>Therefore if you want to convert decimal 5 to binary you would pick the value with the exponent of 2 which is just below the number you would want to get the decimal of. In our case is 4 which is equal to <strong>00000100</strong>.</p>
<p>From their you would want to take the remainder of 5 - 4 = 1, and the binary of 1 as an octet would be <strong>00000001</strong>. Finally when you add the binary numbers for 4 &amp; 1 together, you get</p>
<pre><code class="lang-yaml"><span class="hljs-string">**00000100**</span> <span class="hljs-string">+</span> <span class="hljs-string">**00000001**</span> <span class="hljs-string">=</span> <span class="hljs-string">**00000101**</span>
</code></pre>
<p>We then conclude that decimal number 5 in binary is <strong>00000101</strong>.</p>
<h2 id="heading-network-amp-host-ids">Network &amp; Host IDs</h2>
<p>These 8-bit octets are further divided into 2 main parts, known as the Network-ID, and the Host-ID.</p>
<ul>
<li><p><strong>The Network ID</strong> - Computers on the same network will share the same Network-ID which would be a certain set of number as the first part of your IP address.</p>
<ul>
<li><p>Eg: <mark>76</mark>.<mark>240</mark>.<mark>249</mark>.145, the highlighted numbers <em>could be</em> your network ID. We don’t know this for certain since we have not specified a subnet mask.</p>
</li>
<li><p>The greater the length of the network ID, the lesser the number of assignable host IDs.</p>
</li>
</ul>
</li>
<li><p><strong>Host ID</strong> - The host ID would be the last part of your IP address. This is a unique identifier which is assigned to each computer that’s on your network.</p>
</li>
</ul>
<blockquote>
<p>💡 Note: The Network ID &amp; the Host ID do not have to be divided equally — It’s not always the case that two parts make up the network ID and two address parts make up the host ID.</p>
</blockquote>
<h2 id="heading-subnet-mask">Subnet Mask</h2>
<p>The splitting of an IP address into the Network section and the Host section is done via what is known as a subnet mask.</p>
<p>This is done by what is known as a Classless Inter-Domain Routing (CIDR) notation. The IPv4 address represented by a CIDR notation is known as a network mask and specifies the number of bits in the prefix to the address after a forward slash (/) separator. Eg:</p>
<pre><code class="lang-yaml"><span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.64</span><span class="hljs-string">/24</span>
</code></pre>
<p>When converted to binary the above IP address would look like:</p>
<pre><code class="lang-yaml"><span class="hljs-number">11000000.10101000</span><span class="hljs-number">.00001010</span><span class="hljs-number">.01000000</span><span class="hljs-string">/24</span>
</code></pre>
<p>Therefore the subnet mask specifies that the first 3 octets are going to be the network ID, and the last octet is going to be the host ID.</p>
<p>Now that we know the subnet mask, we can calculate what IPs are available to assign to our hosts.</p>
<p>Starting with</p>
<pre><code class="lang-yaml"><span class="hljs-number">11000000.10101000</span><span class="hljs-number">.00001010</span><span class="hljs-number">.00000001</span> <span class="hljs-string">=</span> <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.1</span>
</code></pre>
<p>and ending with</p>
<pre><code class="lang-yaml"><span class="hljs-number">11000000.10101000</span><span class="hljs-number">.00001010</span><span class="hljs-number">.11111110</span> <span class="hljs-string">=</span> <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.254</span>
</code></pre>
<p>Now you must be wondering why the last assigned host ID is <strong>254</strong> (11111110) and not <strong>255</strong> (11111111), which is the max range of the octet right? Or didn’t even think about it. 😅 Well this is because the last one is reserved as a <strong>broadcast ID.</strong> The last ID, the broadcast ID is assigned for communication purposes between your router and computer, which enables your network to even be a network! Eg: DHCP - Dynamic Host Configuration Protocol.</p>
]]></content:encoded></item><item><title><![CDATA[Steps to Create and Use a Free Custom Email Domain]]></title><description><![CDATA[Already have a domain for your website and don’t want to pay for an custom email address? Then this guide is for you!
First purchase your domain. I would recommend NameCheap, they’re prices are quite reasonable when it comes to domains.
Head over to ...]]></description><link>https://blog.ranindu.com/free-custom-email-domain</link><guid isPermaLink="true">https://blog.ranindu.com/free-custom-email-domain</guid><category><![CDATA[email]]></category><category><![CDATA[Devops]]></category><category><![CDATA[domain]]></category><dc:creator><![CDATA[Ranindu Abeyratne]]></dc:creator><pubDate>Mon, 25 Nov 2024 05:48:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/DCg103FwjCg/upload/c524fadfbb1642290061f0a3bba7e6b2.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Already have a domain for your website and don’t want to pay for an custom email address? Then this guide is for you!</p>
<p>First purchase your domain. I would recommend <a target="_blank" href="http://namecheap.com">NameChea</a>p, they’re prices are quite reasonable when it comes to domains.</p>
<p>Head over to <a target="_blank" href="https://forwardemail.net/en">Forwardemail</a> and enter the domain name you purchased, and the gmail address that you normally use.</p>
<p>After you get your domain with namecheap</p>
<p>Click on Account ⇒ Dashboard ⇒ Domain List ⇒ Manage ⇒ Advanced DNS</p>
<p>Then you create a TXT record</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Type</td><td>Host</td><td>Value</td><td>TTL</td></tr>
</thead>
<tbody>
<tr>
<td>TXT Record</td><td>@</td><td>forward-email={your-username}@gmail.com</td><td>Automatic</td></tr>
</tbody>
</table>
</div><p>After that under Mail Settings, you will need to create 2 custom MX Records</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Type</td><td>Host</td><td>Value</td><td>Priority</td><td>TTL</td></tr>
</thead>
<tbody>
<tr>
<td>MX Record</td><td>@</td><td><a target="_blank" href="http://mx1.forwardemail.net">mx1.forwardemail.net</a>.</td><td>10</td><td>Automatic</td></tr>
<tr>
<td>MX Record</td><td>@</td><td><a target="_blank" href="http://mx2.forwardemail.net">mx2.forwardemail.net</a>.</td><td>10</td><td>Automatic</td></tr>
</tbody>
</table>
</div><p>After you’ve set this up on your namecheap DNS settings, go back to forward mail and verify that the MX records and TXT records are being detected.</p>
<p>After that’s done, you just need to configure your gmail settings.</p>
<p>💡 Important: Remember that you will need to have <a target="_blank" href="https://google.com/landing/2step/">2-factor-authentication setup</a> on your gmail account to proceed with this setup.</p>
<p>If you have your 2FA setup on google, navigate to your app passwords by click on your profile picture circle ⇒ Manage your Google Account ⇒ Security ⇒ App Passwords</p>
<p>Select your App as “Mail” and your device as “Other (Custom name)”</p>
<p>When you are prompted to input text, type in the custom email address that you want for your domain “example@yourdomain.com”.</p>
<p>💡 Important: Copy the auto generated password that has been generated here. It will be needed in the up coming steps.</p>
<p>Now go back to your gmail account and click on the Setting “Cog” and click on “See all settings”.</p>
<p>Account and Imports ⇒ Send mail as: ⇒ Add another email address</p>
<p>Enter the Name that you want to be seen as and the email address of the custom domain that you used when preparing your App. Make sure you uncheck “treat as an alias”.</p>
<p>Click next and configure your email to be sent through your SMTP server.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>SMTP Server</td><td>Username</td><td>Password</td></tr>
</thead>
<tbody>
<tr>
<td><a target="_blank" href="http://smtp.gmail.com">smtp.gmail.com</a></td><td><strong>YOUR</strong> login gmail address without the @gmail.com</td><td>Paste the password that you copied earlier during App creation.</td></tr>
</tbody>
</table>
</div><p>Leave the radio button checked to “Secured connection using TLS” and click on “Add Account”.</p>
<p>Next check your gmail, for a verification email that has been emailed to your inbox. Copy the confirmation code and paste into the verification prompt, then click the “Verify” button. Once that’s done, go back to your verification email and click on the confirmation link that they sent and finally confirm the request.</p>
<p>That’s it. You’re done!</p>
<p>Now when you compose an email you can select your custom domain from the “From” drop down and send emails using your very own domain.</p>
]]></content:encoded></item><item><title><![CDATA[Guide to Auto Scaling in Kubernetes Clusters]]></title><description><![CDATA[Introduction
I wanted to talk about a issue I had to solve regarding auto-scaling Kubernetes with Hetzner Cloud. In this guide, I will walk through my thought process and the process of setting up and configuring auto-scaling for your Kubernetes clus...]]></description><link>https://blog.ranindu.com/guide-to-auto-scaling-in-kubernetes-clusters</link><guid isPermaLink="true">https://blog.ranindu.com/guide-to-auto-scaling-in-kubernetes-clusters</guid><category><![CDATA[Hetzner Cloud]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[Devops]]></category><category><![CDATA[autoscaling]]></category><category><![CDATA[guide]]></category><dc:creator><![CDATA[Ranindu Abeyratne]]></dc:creator><pubDate>Mon, 25 Nov 2024 05:28:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/-S87hxapFvU/upload/0167da6cde3a3f4370495a6a9d709467.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction"><strong>Introduction</strong></h2>
<p>I wanted to talk about a issue I had to solve regarding auto-scaling Kubernetes with Hetzner Cloud. In this guide, I will walk through my thought process and the process of setting up and configuring auto-scaling for your Kubernetes cluster using Hetzner Cloud.</p>
<h3 id="heading-explanation-of-kubernetes-and-hetzner-cloud"><strong>Explanation of Kubernetes and Hetzner Cloud</strong></h3>
<p>Kubernetes is a popular open-source container orchestration platform that automates the deployment, scaling, and management of containerized applications. It allows developers to run and manage their applications across multiple hosts and provides a consistent and reliable way to deploy applications at scale.</p>
<p>Hetzner Cloud, on the other hand, is a cloud hosting provider that offers virtual machines, storage, and networking infrastructure for running and scaling applications. Hetzner Cloud provides a simple and intuitive interface for managing your infrastructure and allows you to easily deploy and scale your applications.</p>
<h3 id="heading-purpose-of-this-documentation"><strong>Purpose of this documentation</strong></h3>
<p>The purpose of this documentation is to help you set up auto-scaling in your Kubernetes cluster using Hetzner Cloud. Auto-scaling is a critical feature for ensuring that your applications can handle fluctuating traffic loads and can scale up or down as needed.</p>
<p>By the end of this guide, you should have a working auto-scaling setup in your Kubernetes cluster and be able to monitor and troubleshoot any issues that may arise. Let's get started!</p>
<h2 id="heading-overview-of-auto-scaling-in-kubernetes"><strong>Overview of auto-scaling in Kubernetes</strong></h2>
<p>Before I dive into setting up auto-scaling in Kubernetes with Hetzner Cloud, let's take a closer look at how auto-scaling works in Kubernetes and the benefits of using it.</p>
<h3 id="heading-benefits-of-auto-scaling"><strong>Benefits of auto-scaling</strong></h3>
<p>Auto-scaling offers several benefits for developers and operations teams. By automatically scaling your application based on demand, you can ensure that your application is always available and responsive to users. You can also save money on infrastructure costs by scaling down when demand is low, rather than running resources at full capacity all the time.</p>
<p>Auto-scaling also simplifies the process of managing and scaling your application, as it removes the need for manual intervention when traffic spikes or drops. This can save time and reduce the risk of errors in manual scaling.</p>
<h3 id="heading-considerations-for-auto-scaling-in-kubernetes"><strong>Considerations for auto-scaling in Kubernetes</strong></h3>
<p>While auto-scaling offers many benefits, there are a few considerations to keep in mind when setting up auto-scaling in Kubernetes.</p>
<p>First, you'll need to ensure that your application is designed to scale horizontally, meaning that it can handle an increasing number of instances without negatively impacting performance. You'll also need to make sure that your application is stateless, as stateful applications can be more difficult to scale.</p>
<p>Second, you'll need to carefully choose the metrics that you use to trigger auto-scaling. If you choose a metric that doesn't accurately reflect the demand for your application, you may end up scaling up or down at the wrong times.</p>
<p>Finally, it's important to monitor your application and the auto-scaling process to ensure that everything is working as expected. This will allow you to quickly identify and resolve any issues that may arise.</p>
<h2 id="heading-setting-up-auto-scaling-in-kubernetes-with-hetzner-cloud"><strong>Setting up auto-scaling in Kubernetes with Hetzner Cloud</strong></h2>
<p>Now that we have a solid understanding of auto-scaling in Kubernetes and the pre-requisites in place, let's walk through the steps for setting up auto-scaling with Hetzner Cloud.</p>
<h3 id="heading-create-a-horizontal-pod-autoscaler"><strong>Create a Horizontal Pod Autoscaler</strong></h3>
<p>Next, you'll create a Horizontal Pod Autoscaler (HPA) for your deployment. The HPA will automatically adjust the number of replicas in your deployment based on the CPU utilization of your pods.</p>
<p>To create an HPA, you'll need to specify the deployment you want to scale, the target CPU utilization, and the minimum and maximum number of replicas. You can find instructions on how to create an HPA in the Kubernetes documentation.</p>
<p>You can use <a target="_blank" href="https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/hetzner/README.md">Kubernetes Cluster Autoscaler</a> to auto scale a Kubernetes cluster in Hetzner Cloud. The cluster autoscaler for Hetzner Cloud scales worker nodes within your cluster. It monitors pod scheduling and decides if there is enough capacity on your cluster to schedule the pods on a node. If there is not enough capacity to run the pods it will call the Hetzner Cloud API and launch new instances and add them to your cluster.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732512129460/b206572b-5f1b-45a6-94bd-7a89545307c0.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-install-the-hetzner-cloud-controller-manager"><strong>Install the Hetzner Cloud Controller Manager</strong></h3>
<p>The first step is to install the Hetzner Cloud Controller Manager, which allows Kubernetes to manage resources in Hetzner Cloud. You can install the Hetzner Cloud Controller Manager using the instructions in the <a target="_blank" href="https://github.com/hetznercloud/hcloud-cloud-controller-manager">Hetzner Cloud documentation</a>.</p>
<h3 id="heading-create-a-kubernetes-deployment"><strong>Create a Kubernetes deployment</strong></h3>
<p>Once the Hetzner Cloud Controller Manager is installed, you can create a Kubernetes deployment for your application. This deployment should include a minimum and maximum number of replicas, which will be used by the Horizontal Pod Autoscaler to scale up and down as needed.</p>
<h3 id="heading-test-the-auto-scaling-setup"><strong>Test the auto-scaling setup</strong></h3>
<p>Once your deployment and HPA are set up, you can test the auto-scaling setup by generating load on your application. You can use a tool like ApacheBench/ Apache Jmeter to generate load on your application, and monitor the number of replicas in your deployment to ensure that the HPA is scaling up and down as expected.</p>
<h2 id="heading-best-practices-for-auto-scaling-in-kubernetes-with-hetzner-cloud"><strong>Best practices for auto-scaling in Kubernetes with Hetzner Cloud</strong></h2>
<p>While setting up auto-scaling in Kubernetes with Hetzner Cloud is relatively straightforward, there are some best practices that can help you optimize your auto-scaling setup for performance, reliability, and cost.</p>
<h3 id="heading-monitor-your-application-and-auto-scaling-setup"><strong>Monitor your application and auto-scaling setup</strong></h3>
<p>It's important to monitor your application and the auto-scaling setup to ensure that everything is working as expected. This will allow you to quickly identify and resolve any issues that may arise.</p>
<p>You should monitor key metrics such as CPU utilization, memory usage, and network traffic, as well as the number of replicas in your deployment and the HPA behavior. You can use tools like Prometheus and Grafana to monitor your Kubernetes cluster and application metrics.</p>
<h3 id="heading-optimize-your-application-for-auto-scaling"><strong>Optimize your application for auto-scaling</strong></h3>
<p>To ensure optimal performance and scalability, it's important to optimize your application for auto-scaling. This includes designing your application to be stateless and horizontally scalable, as well as using caching and load balancing to distribute traffic evenly across instances.</p>
<p>You should also consider optimizing your application for the specific infrastructure in Hetzner Cloud, such as using Hetzner Cloud Load Balancers to distribute traffic or using Hetzner Cloud Volumes for persistent storage.</p>
<h3 id="heading-plan-for-cost-optimization"><strong>Plan for cost optimization</strong></h3>
<p>Auto-scaling can help you save money on infrastructure costs by scaling down resources when they're not needed, but it's still important to plan for cost optimization. This includes setting up cost alerts to monitor your spending, as well as using spot instances and reserved instances to save money on compute resources.</p>
<p>You should also consider optimizing your application for cost, such as using smaller instance types or reducing the number of replicas when demand is low.</p>
<p>By following these best practices, you can ensure that your auto-scaling setup in Kubernetes with Hetzner Cloud is optimized for performance, reliability, and cost.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Auto-scaling in Kubernetes with Hetzner Cloud can provide significant benefits in terms of performance, reliability, and cost savings. By following best practices such as monitoring your application and auto-scaling setup, optimizing your application for auto-scaling, using multiple availability zones for high availability, and planning for cost optimization, you can ensure that your auto-scaling setup is optimized for your specific needs.</p>
<p>However, there are also challenges to consider, such as the complexity of the auto-scaling setup, difficulty in predicting resource needs, performance impact of auto-scaling, and cost management. By addressing these challenges and implementing best practices, you can mitigate the risks associated with auto-scaling and ensure that your application is running smoothly and cost-effectively.</p>
<p>In summary, auto-scaling in Kubernetes with Hetzner Cloud requires a combination of technical knowledge, monitoring and optimization, and cost management skills. By investing time and effort in learning and implementing these best practices, you can take full advantage of the benefits of auto-scaling and ensure that your application is running at its best.</p>
]]></content:encoded></item><item><title><![CDATA[Setting Up Self-Hosted Runners in GitHub: A Step-by-Step Guide]]></title><description><![CDATA[Imagine your boss wants you to use Github actions to automate the workflow of the developers. So you find this thing called Github actions and on paper it seems like the perfect solution, so you get working on it and find out that the signal to trigg...]]></description><link>https://blog.ranindu.com/setting-up-self-hosted-runners-in-github-a-step-by-step-guide</link><guid isPermaLink="true">https://blog.ranindu.com/setting-up-self-hosted-runners-in-github-a-step-by-step-guide</guid><category><![CDATA[GitHub]]></category><category><![CDATA[automation]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Ranindu Abeyratne]]></dc:creator><pubDate>Mon, 25 Nov 2024 05:16:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/atSaEOeE8Nk/upload/5827708b4bde425424897ef590438e8e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Imagine your boss wants you to use Github actions to automate the workflow of the developers. So you find this thing called Github actions and on paper it seems like the perfect solution, so you get working on it and find out that the signal to trigger the job in your pipeline is sent from one of Github’s servers.<br />Now the problem is that your pipeline servers are locked behind a VPN, but Github always sends the trigger signal from 1 of the 1000 IPs that they own. The self-hosted Github runners come in handy in this situation, after setting up a custom runner in a server that has a whitelisted IP in your pipeline server, you will now be able to access automate the action for your developers to trigger straight from their code base.</p>
<p>The installation of a Github runner can be done at 3 levels:</p>
<ul>
<li><p>Enterprise</p>
<ul>
<li>These runners can be assigned to all organizations and repositories.</li>
</ul>
</li>
<li><p>Organization</p>
<ul>
<li>These runners can be assigned to all repositories under that organization.</li>
</ul>
</li>
<li><p>Repository</p>
<ul>
<li>Runners that are assigned to a specific repository.</li>
</ul>
</li>
</ul>
<p>Once the runners have been assigned you can then use labels to choose which job you want running on a specific GH runner</p>
<p>All 3 runners are configured using similar steps, but this guide will focus on installing it at an Organization level.</p>
<aside>
💡 Important to note that in order for this to work, you will need an existing server with your favorite cloud provider or an on-prem one.
</aside>

<p>First you will have to click on the drop down next to your profile picture and go into “Your organizations”</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732510834571/b6786cb6-6d76-47ff-a0bd-2fe279562f48.png" alt class="image--center mx-auto" /></p>
<p>Next click on the “Settings” button next to your organization’s name.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732510893650/dd9c3696-2687-428c-b2ce-cb87cd00bd0c.png" alt class="image--center mx-auto" /></p>
<p>Go to “Actions” &amp; “Runners”</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732510913966/57648dda-706a-4a83-9261-2632249c77ff.png" alt class="image--center mx-auto" /></p>
<p>Then create a new self-hosted runner.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732510927911/e627ec17-93a2-42f7-a4d7-1bdcd0e0a07c.png" alt class="image--center mx-auto" /></p>
<p>Next choose your operating system and run the commands that they have displayed within your server that you want to host the runner on.</p>
<p>The following steps are to help you configure a GH runner on a Linux x64 operating system.</p>
<aside>
💡 <strong>Note</strong>: When proceeding from here you will need to use a non-root user in your operating system as the runner configuration step is not allowed to be executed as root user.
</aside>

<h3 id="heading-configure-linux-user">Configure Linux User</h3>
<p>These commands will be executed assuming you’re running as root.</p>
<p>Create a new user</p>
<pre><code class="lang-json"><span class="hljs-comment">//This will create a the user runner and set their home directory to their name.</span>
<span class="hljs-comment">//Replace "runner" with desired username.</span>
adduser -m runner
</code></pre>
<p>Create user password</p>
<pre><code class="lang-json"><span class="hljs-comment">//Replace "runner" with desired username.</span>
passwd runner
</code></pre>
<p>Switch to the non-root user</p>
<pre><code class="lang-json">su runner
</code></pre>
<h3 id="heading-install-amp-run-gh-runner">Install &amp; Run GH Runner</h3>
<p>Creates a folder and cd into it</p>
<pre><code class="lang-json">mkdir actions-runner; cd actions-runner
</code></pre>
<p>Download the runner package</p>
<pre><code class="lang-json">curl -o actions-runner-linux-x64<span class="hljs-number">-2.297</span><span class="hljs-number">.0</span>.tar.gz -L https:<span class="hljs-comment">//github.com/actions/runner/releases/download/v2.297.0/actions-runner-linux-x64-2.297.0.tar.gz</span>
</code></pre>
<p>Optional: Validate the hash</p>
<pre><code class="lang-json">echo <span class="hljs-string">"eb4e2fa6567d2222686be95ee23210e83fb6356dd0195149f96fd91398323d7f  actions-runner-linux-x64-2.297.0.tar.gz"</span> | shasum -a <span class="hljs-number">256</span> -c
</code></pre>
<p>Extract the installer from the .tar file you just downloaded.</p>
<pre><code class="lang-json">tar xzf ./actions-runner-linux-x64<span class="hljs-number">-2.297</span><span class="hljs-number">.0</span>.tar.gz
</code></pre>
<p>Create the runner and start the configuration</p>
<aside>
💡 <strong>Note</strong>: Executing the following config command will prompt you for config details.
- Add to group can be left as default
<ul>
<li>
<p>Name of your runner can be named as you like or leave the default option</p>
</li>
<li>
<p>Label option is an important one. By default you are given a set of default labels. But if you intend on using multiple runners, then add the appropriate labels so that you can refer to them in your workflow files to target and access your self-hosted runners.</p>
</li>
<li>
<p>Finally the name and path of your work folder. This is where it’s going to to download the actions that it runs and do all of it’s work. You can leave that with the default option which is &lt;_work&gt;.</p>
</li>
</ul>
</aside>

<pre><code class="lang-json">./config.sh --url https:<span class="hljs-comment">//github.com/&lt;YOUR_ORGANIZATION&gt; --token &lt;GITHUB_PROVIDED_TOKEN&gt;</span>
</code></pre>
<aside>
💡 <strong>Note</strong>: The URL will contain the name of your organization at the end. The GITHUB_PROVIDED_TOKEN will be automatically generated by Github when you initially create the runner. It’s important to note that this token has an expiration time of 1 hour so if you take too long, you will have to refresh and generate a new one.
</aside>

<p>Run it! (This is a manual process and you will have to run this manually each time)</p>
<pre><code class="lang-json">./run.sh
</code></pre>
<p>Note that the directory you installed the GH runner will have a <a target="_blank" href="http://svc.sh">svc.sh</a> file this file can be executed to have your GH runner always operating in case of a server restart as well, instead of always having to manually run the ./run.cmd command.</p>
<p>The <a target="_blank" href="http://svc.sh">svc.sh</a> file can only be executed as root user.</p>
<pre><code class="lang-json">su root
</code></pre>
<p>Change to your installation directory.</p>
<pre><code class="lang-json">cd /home/runner/actions-runner
</code></pre>
<p>Install the service</p>
<pre><code class="lang-json">./svc.sh install
</code></pre>
<p>Start your service</p>
<pre><code class="lang-json">./svc.sh start
</code></pre>
<p>Now if you go back to your runners tab within your organization in your Github account, you will be able to note that your self-hosted runner is now in <strong>idle</strong> status. This means that you currently don’t have any actions running, and that your server is listening for any incoming actions.</p>
]]></content:encoded></item><item><title><![CDATA[Understanding the Basics of Terraform]]></title><description><![CDATA[An open source infrastructure as code (IaC) software tool that allows DevOps engineers to programmatically provision the physical resources an application requires to run.
What is Terraform?
Terraform is an open source and declarative tool for infras...]]></description><link>https://blog.ranindu.com/understanding-the-basics-of-terraform</link><guid isPermaLink="true">https://blog.ranindu.com/understanding-the-basics-of-terraform</guid><category><![CDATA[Terraform]]></category><category><![CDATA[basics]]></category><category><![CDATA[Commands]]></category><dc:creator><![CDATA[Ranindu Abeyratne]]></dc:creator><pubDate>Mon, 25 Nov 2024 00:30:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/gkj_KRTLJSs/upload/5eaabecb5596aaaf1c6edb4f476a662c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>An open source infrastructure as code (IaC) software tool that <strong>allows DevOps engineers to programmatically provision the physical resources an application requires to run</strong>.</p>
<h2 id="heading-what-is-terraform">What is Terraform?</h2>
<p>Terraform is an open source and declarative tool for infrastructure provisioning. It let’s you manage your infrastructure and the platform and services that run on that infrastructure.</p>
<p>A good example of this would be, when you start your own project, say that you would need</p>
<ul>
<li><p>3 servers</p>
<ul>
<li><p>Private network space</p>
</li>
<li><p>Install Docker and other tools on each server</p>
</li>
<li><p>Security Setup | Firewalls</p>
</li>
</ul>
</li>
<li><p>5 microservices as docker containers</p>
</li>
</ul>
<p>Therefore, first you would need to provision your infrastructure by setting up a space to deploy you application. Terraform would be used to set up that space to deploy your microservices. These have to be created in a certain order since there might be task that is dependant on another.</p>
<h2 id="heading-how-does-it-work">How does it work?</h2>
<p>For terraform to work, it needs to connect with your IaaS provider such as AWS, GCP, Azure, Hetzner, Linode etc. For terraform to do this, it has two main components that make up its architecture:</p>
<ul>
<li><p>The Core</p>
<ul>
<li><p>The core uses two inputs to do its job. Using these it figures out the plan to get things done.</p>
<ul>
<li><p><strong>Terraform configuration</strong>, where you as a user would write and define what needs to be created or provisioned.</p>
</li>
<li><p><strong>Terraform state</strong>, is where it keeps the up to date state of how the current setup of your infrastructure looks like.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p>The Provider</p>
<ul>
<li>Terraform uses providers such as IaaS or PaaS providers. Terraform has hundreds of providers which give terraform access to their resources.</li>
</ul>
</li>
</ul>
<p>It is helpful to note that when writing terraform code, it uses a declarative approach, as opposed to an imperative one. When using a declarative approach, you tell terraform what the end result you would want to see is. Eg: I want 3 servers with the specified network config.</p>
<h1 id="heading-commands">Commands</h1>
<hr />
<p>Set TF_LOG variable to check for Terraform logs during TF build or destroy.</p>
<pre><code class="lang-plaintext">TF_LOG=TRACE
</code></pre>
<p>Initialize Terraform to start working.</p>
<pre><code class="lang-plaintext">terraform init
</code></pre>
<p>Get list of terraform workspaces</p>
<pre><code class="lang-plaintext">terraform workspace list
</code></pre>
<p>Check if the changes you’ve made are working before applying/ destroying terraform.</p>
<pre><code class="lang-plaintext">terraform plan
</code></pre>
<p>Query’s the provider to get an up to date state.</p>
<pre><code class="lang-plaintext">terraform refresh
</code></pre>
<p>Creates new TF workspace</p>
<pre><code class="lang-plaintext">terraform workspace new &lt;ws_name&gt;
</code></pre>
<p>Select a TF workspace to work on.</p>
<pre><code class="lang-plaintext">terraform workspace select &lt;ws_name&gt;
</code></pre>
<p>Deletes an existing TF workspace.</p>
<pre><code class="lang-plaintext">terraform workspace delete &lt;ws_name&gt;
</code></pre>
<hr />
]]></content:encoded></item><item><title><![CDATA[Comprehensive Guide to Setting Up Alerts in Grafana for Effective Monitoring]]></title><description><![CDATA[Dashboard Config & Alert Setup
Dashboard Setup
When creating alerts the important thing to note is that alerts can only be created for a Time Series panel type.
After an alert is configured to a panel the green heart that's displayed will show the st...]]></description><link>https://blog.ranindu.com/msteams-grafana-alerting-guide</link><guid isPermaLink="true">https://blog.ranindu.com/msteams-grafana-alerting-guide</guid><category><![CDATA[Grafana]]></category><category><![CDATA[alerting]]></category><category><![CDATA[Devops]]></category><category><![CDATA[observability]]></category><category><![CDATA[MS Teams]]></category><category><![CDATA[metrics]]></category><dc:creator><![CDATA[Ranindu Abeyratne]]></dc:creator><pubDate>Sun, 24 Nov 2024 15:45:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/JKUTrJ4vK00/upload/7a81924544ac0b5e531f08006279c3ba.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-dashboard-config-amp-alert-setup">Dashboard Config &amp; Alert Setup</h2>
<h4 id="heading-dashboard-setup">Dashboard Setup</h4>
<p>When creating alerts the important thing to note is that alerts can only be created for a <strong>Time Series</strong> panel type.</p>
<p>After an alert is configured to a panel the green heart that's displayed will show the state of the alert. Green=OK, Yellow=Pending &amp; Red=Firing.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732462599612/43be036b-7230-4f88-9123-1b6c3e02acbb.png" alt /></p>
<h4 id="heading-query-definition">Query Definition</h4>
<p>The query shown below gathers the node state in a kubernetes cluster. Rule Type is left as Grafana managed since we use Grafana to send out all our alerts.</p>
<p>Expression is where you define your alert condition. Here we say it to send out an alert if the <em>last value</em> from the query falls outside the range of 1, where 1 is the Ready state.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732462626280/68f180af-34b7-4c89-96ad-09cbb98d280d.png" alt /></p>
<h4 id="heading-evaluation-behavior">Evaluation Behavior</h4>
<p>Select or create a new folder which makes sense for you, since the node ready status is related to the cluster nodes, it's going to be grouped with other node metrics.</p>
<p>The Evaluation Group is important since all alerts which are put into that group will be evaluated at the same time. This will ensure that all Ready Status Checks are evaluated every minute at the same minute. You can always change the evaluation interval if you so desire.</p>
<p>The pending period is how long you want to evaluate it for before the alert is triggered. It's good practice to have the pending period be more than you evaluation group interval. This is so that an alert will not be triggered if there was an error receiving the data.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732462650395/e2ec39b8-b4b3-4cae-90ac-320362dd9a60.png" alt /></p>
<h4 id="heading-annotations-amp-labels">Annotations &amp; Labels</h4>
<p>The annotations can be used to mention what the alert is about to the team members that you want to send it out to.</p>
<p>The labels are important as these are needed when you configure your alerting contact points and notification policies. Make sure you have labels that make sense. eg: severity=critical, contact_point=email, team=devops, or environment=staging. These labels can then be used to filter out your alerts when sending them through.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732462681851/7bb34a07-3c44-4485-ae04-ca26b753f5ab.png" alt /></p>
<h2 id="heading-setting-up-contact-points">Setting Up Contact Points</h2>
<h4 id="heading-email">Email</h4>
<p>To set up email capabilities in grafana refer to [[Grafana Emails]]. Once, emailing has been configured you can send a test alert from the contact point to ensure that it's functioning as expected as shown below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732462716422/a4800c4d-62cc-42fc-b352-95a705dee878.png" alt /></p>
<h4 id="heading-ms-teams">MS Teams</h4>
<p>To configure a contact point for Microsoft Teams, you will first need to get a webhook from the teams channel that you want the alerts to go to. This can be done by going into <em>Manage Channel</em> and clicking on the <em>Edit</em> button under <strong>Connectors</strong>. Then search for <em>Incoming Webhooks</em> and follow the steps and copy the webhook URL.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732462783163/ea910914-9363-4a84-9ed7-adc84c9a35c7.png" alt /></p>
<p>Once you have copied the webhook, you can proceed with the grafana setup shown below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732462810128/79d1c1c8-d909-47da-862a-ed2515193002.png" alt /></p>
<h2 id="heading-notification-policies">Notification Policies</h2>
<p>The last thing you have to configure in order to make sure your alerts are going to functioning as expected are the notification policies.</p>
<p>It's important to note that when creating nested policies, the first policy which matches the alert label will be triggered and the alert will not go further down the policy tree. If you want the alert labels to continue matching down the policy tree you will have to check the <em>Continue matching subsequent sibling nodes</em> toggle on. This will ensure that the alert labels continue to match the policies down the notification policy tree. This would be a required toggle if you wish to send your alerts to multiple contact points at the same time.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732462840748/7d38c00c-f5bb-4502-845e-096c18f6790e.png" alt /></p>
<p>Things to keep in mind when configuring the policies:</p>
<p><strong>Default Policy</strong>: This is the policy that's matched when an alert <strong>DOES NOT</strong> match any of the labels you have configured in your alerts.</p>
<p><strong>Nested Policy</strong>: These are what you will be creating. Here you define what matching labels you want to send to a contact point of your choosing. The image below uses a regex expression to match severity level warning or critical labels from the firing alert and then send it over the MS Teams contact point which was configured earlier.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732462882943/9a2730d8-c64a-4e49-9bc6-4f5ffe849557.png" alt /></p>
]]></content:encoded></item><item><title><![CDATA[Introduction To Docker]]></title><description><![CDATA[“It works on mine, so it works everywhere.” ~ Ranindu Abeyratne ~

What is Docker & why should I use it?
Docker is a set of the platform as a service (PaaS) products that use OS-level virtualization to deliver software in packages called containers.
...]]></description><link>https://blog.ranindu.com/introduction-to-docker</link><guid isPermaLink="true">https://blog.ranindu.com/introduction-to-docker</guid><category><![CDATA[Devops]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Developer]]></category><category><![CDATA[infrastructure]]></category><dc:creator><![CDATA[Ranindu Abeyratne]]></dc:creator><pubDate>Tue, 03 May 2022 05:28:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/tjX_sniNzgQ/upload/v1651553748442/BtU5wVdzG.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p><em>“It works on mine, so it works everywhere.” ~ Ranindu Abeyratne ~</em></p>
</blockquote>
<h2 id="heading-what-is-docker-andamp-why-should-i-use-it">What is Docker &amp; why should I use it?</h2>
<p>Docker is a set of the platform as a service (PaaS) products that use OS-level virtualization to deliver software in packages called containers.</p>
<p>Yea, but what does that mean?</p>
<p>Well, docker solves an age-old problem where you would hear one person say “the code doesn’t work!”, and the other would reply with “...well it works on my computer”.</p>
<p>What docker does is that it would use a set of instructions defined in what’s known as a Dockerfile, to run your application on a specified operating system (OS). Unlike a regular VM, Docker only virtualizes the OS and uses the available hardware, which makes everything run faster! This can then be used in any local machine of your choice. There are 3 fundamental elements that enable Docker:</p>
<ul>
<li>Dockerfile</li>
<li>Image</li>
<li>Container</li>
</ul>
<h2 id="heading-what-are-docker-images-andamp-containers">What are Docker Images &amp; Containers?</h2>
<p>The first thing you will do after you have written a Dockerfile for your application is build the image using a <em>docker build</em> command. Once the image is built, you could use that image to create your container. Running the image multiple times will create multiple clones of a container.</p>
<p>A container is what holds your application. Think of this as a virtual computer. A container will run its own OS and run your application inside it. Therefore anyone using an image you built or a container that you created would have your application without facing any sort of OS-level restrictions or issues.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651554313688/zEMfRqyZ7.png" alt="Untitled_Diagram.png" /></p>
<p>Docker Container Running On Different OS’s</p>
<h2 id="heading-getting-started">Getting Started</h2>
<p>By now you should have a rough idea of how Docker works. Now here’s how to put it into practice. First thing’s first, you will need a simple application to work with. I will go ahead and create a sample React application to “containerize” it.</p>
<p>Open up your terminal, and cd (change directory) into a directory/folder where you would want to create your application. Then to get started you would want to run:</p>
<pre><code>npx create<span class="hljs-operator">-</span>react<span class="hljs-operator">-</span>app my_react_app
</code></pre>
💡 Note: For this to work you will need <a target="_blank" href="https://nodejs.org/en/download/">Node.js</a> installed on your device &amp; of course you will need to have docker installed to follow along.



<p>Congratulations, you now have a basic react app to work with.</p>
<p>Next, open the application you installed with your favorite text editor and create a “Dockerfile”.</p>
<p>Your directory structure should now look something like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651554284151/qv-HbTtOk.png" alt="Untitled.png" /></p>
<h2 id="heading-the-dockerfile">The Dockerfile</h2>
<p>Now that you have your Dockerfile created, we can look into what it does. The Dockerfile gives a specific set of instructions for what goes into your container. You can specify things such as OS version, and how to build and run your application. Here’s a simple Dockefile.</p>
<pre><code><span class="hljs-keyword">FROM</span> node:<span class="hljs-number">16</span>

<span class="hljs-comment"># Default directory for your app</span>
WORKDIR /usr/src/app

<span class="hljs-comment"># Install app dependencies</span>
<span class="hljs-comment"># A wildcard is used to ensure both package.json AND package-lock.json are copied</span>
<span class="hljs-comment"># where available (npm@5+)</span>
COPY package*.json ./

RUN npm install
<span class="hljs-comment"># If you are building your code for production</span>
<span class="hljs-comment"># RUN npm ci --only=production</span>

<span class="hljs-comment"># Bundle app source</span>
COPY . .

EXPOSE <span class="hljs-number">8080</span>
CMD [ <span class="hljs-string">"npm"</span>, <span class="hljs-string">"run"</span>, <span class="hljs-string">"start"</span> ]
</code></pre><p>The commands you want Docker to execute during build time are written in all caps for readability purposes.</p>
<p><strong>FROM</strong> - This defines what type of OS you want the container to have. A list of all the official images can be found <a target="_blank" href="https://hub.docker.com/search?type=image&amp;image_filter=official">here</a>. In this example, we want to have an image with Node.Js already in it as we’re working with a react application.</p>
<p><strong>WORKDIR</strong> - This is your default directory, you can specify a path you want here and it will create the needed directories. From this point onwards docker will treat this as its home location. Therefore, unless explicitly defined, anything you copy or add will go into this location.</p>
<p><strong>COPY</strong> - This command takes 2 arguments. The first one is what to copy from your LOCAL device, and the second argument is where to paste it inside your container. So when you define </p>
<pre><code>COPY package<span class="hljs-operator">*</span>.json ./
COPY . .
</code></pre><p>this means that you want the build to copy both your package.json and your package-lock.json into your home location which is defined using WORKDIR. The wildcard (* icon) means that you want both package.json and package-lock.json to be copied. The second COPY . . means copy all of the content from your local device (the directory where your Dockerfile is located) into your container WORKDIR location. </p>
<p><strong>RUN</strong> - This is used when you want to execute a terminal command inside the container. Since you already copied your package files, you can now ask docker to run npm install, and it will install all your dependencies that are found inside your package.json file. You can run a command like <em>mkdir</em> if you wanted to create a new directory for some reason.</p>
<p><strong>EXPOSE</strong> - This tells Docker to get all of the required information during the runtime from a specified Port and exposes that port. Therefore communication cannot occur through any other port since those will all be closed.</p>
<p><strong>CMD</strong> - Finally the CMD command is to let Docker know to start the application and end the build from there. You might be asking why not use RUN? When you use RUN and your application starts and says that it’s listening on PORT 8080, Docker will wait until it finishes listening to stop the build. This means it will never stop unless you manage to Ctrl+C the build, and that would result in a failed build.</p>
<h2 id="heading-the-build">The Build</h2>
<p>Once the Dockerfile is in place. You can run the build command </p>
<pre><code><span class="hljs-attribute">docker</span> build -t my-first-image:<span class="hljs-number">1</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span> .
</code></pre><p>“docker build” defines what you want docker to do, which in this case would be to build your image. The “-t” means to tag the image so you can give it a name and a version number if you want. Finally, the “.” at the end of the command is to tell docker where your Dockerfile is located.</p>
<p>Once your image is done building you can now check out your latest creation using</p>
<pre><code><span class="hljs-attribute">docker</span> images
</code></pre><p>which should show you something like this</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651554914896/cO5PP2syy.png" alt="Docker_images.png" /></p>
<p>Your first reaction should be “WTF?! Why is a simple react app 1.37Gb?”</p>
<p>Well, that’s because the initial image you defined in your Dockerfile is “FROM node:16”. This comes with a lot of unwanted packages and libraries, which can also lead to security issues. There are a few ways you can fix this. One, by using a lightweight distro like <em>alpine</em>, or you could use a <em>multi-stage docker build</em>. However, I will leave that topic for a different article.</p>
<p>After you’ve built the image you can now run it using</p>
<pre><code><span class="hljs-attribute">docker</span> run -d <span class="hljs-number">8</span>f<span class="hljs-number">0</span>
</code></pre><p>you ask docker to run it in “-d” <em>detached mode.</em> This is to avoid docker from showing you the logs and not being able to escape it without hitting Ctrl + C. You can copy paste the IMAGE ID, or you can just use the first 3 characters of the ID as long as it’s unique and not shared by another image ID.</p>
<p>Now that your container’s running, you will be able to hit <a target="_blank" href="http://localhost">localhost</a> on your browser and look at what you’ve achieved so far. If you’ve exposed a different port other than 8080. You can try with localhost:.</p>
<p>Congratulations! You’ve taken your first step into the world of DevOps.</p>
<h2 id="heading-cheatsheet">Cheatsheet</h2>
<p>I thought it would be nice to leave you guys with a cheat sheet that I made to refer to when I had to work with containers a bit. Some of these commands appear in common use cases.</p>
<p><a target="_blank" href="https://drive.google.com/file/d/1gr8QJoRfQX08Rnp4_YyQdS7nusjZAId2/view?usp=sharing">Download Cheat Sheet</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651554554756/CGrE8xuxj.png" alt="Green Basic Geometry Formulas 2D and 3D Infographic.png" /></p>
]]></content:encoded></item></channel></rss>