<?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[Under the Hood by Tanmay]]></title><description><![CDATA[A place where I share what I learn while exploring technology and building projects. You will find articles on Full-Stack Development, LLM Engineering, AI, Linu]]></description><link>https://tanmayydev.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6acb20b64616d9559a0b65ed/b7199e64-057a-40da-921c-05727c327633.png</url><title>Under the Hood by Tanmay</title><link>https://tanmayydev.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 22 Oct 2026 01:38:06 GMT</lastBuildDate><atom:link href="https://tanmayydev.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[From Express to AWS: Building My First CI/CD Pipeline]]></title><description><![CDATA[I built a CI/CD pipeline for an Express API with GitHub Actions, Docker, and AWS EC2. Here's how I put it together, from testing code to deploying it.]]></description><link>https://tanmayydev.hashnode.dev/from-express-to-aws-building-my-first-ci-cd-pipeline</link><guid isPermaLink="true">https://tanmayydev.hashnode.dev/from-express-to-aws-building-my-first-ci-cd-pipeline</guid><category><![CDATA[Devops]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[Docker]]></category><category><![CDATA[AWS]]></category><dc:creator><![CDATA[Tanmay Vishwakarma]]></dc:creator><pubDate>Sat, 17 Oct 2026 18:01:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6acb20b64616d9559a0b65ed/726d41a4-f81b-4281-a4bc-911dd7b3d280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Over the past few months, I've been exploring different technologies around Linux, Docker, Kubernetes, and AWS. Along the way, I wanted to dive into another important part of DevOps: <strong>CI/CD</strong>.</p>
<p>I'd come across this term quite often, but I wanted to understand how it actually works by building something myself. So, I decided to build my first CI/CD pipeline from scratch rather than just learning the concepts.</p>
<p>But before getting into the project, let's understand what CI/CD means.</p>
<p><strong>CI/CD stands for Continuous Integration and Continuous Delivery/Deployment.</strong> In simple terms, it automates the process of testing, building, and delivering an application whenever its code changes.</p>
<p>Without CI/CD, we'd need to test our code, build the application, and deploy updates manually. This can become repetitive and time-consuming as projects grow.</p>
<p>Here's how the two parts work:</p>
<ul>
<li><p><strong>Continuous Integration (CI):</strong> Automatically runs checks and tests when code changes are pushed or a pull request is opened.</p>
</li>
<li><p><strong>Continuous Delivery/Deployment (CD):</strong> Automates preparing an application for release or deploying it to a server.</p>
</li>
</ul>
<p>The key difference between Continuous Delivery and Continuous Deployment is that Continuous Delivery may still require manual approval before release, while Continuous Deployment automatically releases changes that pass the required checks.</p>
<p>The idea is simple: automate repetitive steps and make the journey from code changes to a working application more reliable.</p>
<p>And that's exactly what I wanted to explore through this project: how different tools fit together to take an application from my local machine to a cloud server.</p>
<h2>The Technologies Behind My Pipeline</h2>
<p>Before getting into the implementation, let's look at the technologies I used and the role each plays in my pipeline.</p>
<ul>
<li><p><strong>Node.js and Express.js:</strong> Used to build a simple task-management REST API.</p>
</li>
<li><p><strong>Docker:</strong> Packages the application and its dependencies into an image that can run on another machine.</p>
</li>
<li><p><strong>GitHub Actions:</strong> Automates testing, building and publishing the Docker image, and triggering deployments.</p>
</li>
<li><p><strong>GitHub Container Registry (GHCR):</strong> Stores the Docker images so EC2 can download them during deployment.</p>
</li>
<li><p><strong>AWS EC2:</strong> Provides the cloud server where my Docker container runs.</p>
</li>
<li><p><strong>AWS IAM and OIDC:</strong> Handle secure authentication and permissions between GitHub Actions and AWS without storing permanent AWS access keys in GitHub Secrets.</p>
</li>
<li><p><strong>AWS Systems Manager (SSM):</strong> Lets GitHub Actions send deployment commands to EC2 without relying on SSH.</p>
</li>
</ul>
<p>Each tool has its own role, and the goal of this project was to connect them into one working pipeline.</p>
<p>Let's start with the application itself!</p>
<h2>#1) Building the Express REST API</h2>
<p>Before building the pipeline, I needed an application to deploy. Since my main goal was to learn CI/CD, I kept the application simple and built a task-management REST API using Node.js and Express.js.</p>
<p>The API supports basic CRUD operations: creating, fetching, updating, and deleting tasks. I also added input validation, a <code>/health</code> endpoint, and automated tests.</p>
<p>I skipped the database for now and stored tasks in memory, keeping the setup simple so I could focus on the deployment workflow.</p>
<p>With the API working locally and the tests passing, the next step was to package it using Docker so it could run on another machine without depending on my local setup.</p>
<h2>#2) Dockerizing the Application</h2>
<p>I wanted to package everything my application needed to run into a Docker image instead of relying on the Node.js setup and dependencies available on my laptop.</p>
<p>A <strong>Docker image</strong> is a packaged template, while a <strong>container</strong> is a running instance of that image.</p>
<p>To build the image, I created a <code>Dockerfile</code> containing the necessary instructions. I also added a <code>.dockerignore</code> file to exclude unnecessary files, such as local dependencies and Git history, from the build context.</p>
<p>Once the image was built, I ran a container and tested the API to make sure everything worked as expected before moving to the cloud.</p>
<p>With the application running successfully inside Docker, the next step was to automate the testing process using GitHub Actions.</p>
<h2>#3) Automating Tests with GitHub Actions</h2>
<p>With the application running in Docker, I moved on to automating its tests using <strong>GitHub Actions</strong>.</p>
<p>GitHub Actions lets us automate tasks through workflows that run in response to events such as code pushes and pull requests. I configured mine to run whenever code is pushed to <code>main</code> or a pull request targets that branch. The workflow sets up Node.js, installs dependencies, and runs my automated tests.</p>
<p>I also made the image-building job depend on the test job, so if the tests fail, the pipeline stops before publishing the Docker image or proceeding with deployment.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6acb20b64616d9559a0b65ed/1a00e8aa-e203-4424-bdc1-67a0d9d7821b.png" alt="" style="display:block;margin:0 auto" />

<p>With testing automated, the next step was to publish the Docker image somewhere my EC2 server could access it.</p>
<h2>#4) Publishing the Docker Image to GHCR</h2>
<p>After automating the tests, I needed a place to store the Docker image so my EC2 server could download it during deployment. For this, I used <strong>GitHub Container Registry (GHCR)</strong>, which lets us store and distribute container images.</p>
<p>I updated my GitHub Actions workflow to build and publish the image only after the tests passed and the code was pushed to <code>main</code>.</p>
<p>I used two image tags: <code>latest</code> and the Git commit SHA. While <code>latest</code> identifies the most recently published version, the commit SHA lets me identify and deploy the image built from a specific commit.</p>
<p>For authentication, I used GitHub Actions' built-in <code>GITHUB_TOKEN</code> with the required package permissions, avoiding the need for a separate token to publish images.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6acb20b64616d9559a0b65ed/330aac67-2e91-4621-8976-ee7f62140556.png" alt="" style="display:block;margin:0 auto" />

<p>With this in place, successful pushes to <code>main</code> could automatically produce and publish tested Docker images to GHCR.</p>
<h2>#5) Setting Up AWS EC2 for My Application</h2>
<p>With my Docker image available on GHCR, I needed a server to run it. For this, I used <strong>Amazon EC2</strong> (Elastic Compute Cloud), an AWS service that lets us rent virtual servers in the cloud.</p>
<p>Think of an EC2 instance as a computer running in AWS. I can choose an operating system, install software, and run applications on it without depending on my laptop being switched on.</p>
<p>I launched an EC2 instance running Ubuntu Server and installed Docker on it. This gave me a Linux server where I could pull my Docker image from GHCR and run the container.</p>
<h3>Configuring the Security Group</h3>
<p>I also needed to control which network connections could reach my server. That's where <strong>security groups</strong> come in.</p>
<p>A security group acts like a virtual firewall for an EC2 instance, using rules to control incoming and outgoing network traffic.</p>
<p>Initially, I configured these inbound rules:</p>
<ul>
<li><p><strong>HTTP (port 80):</strong> Allows users to send HTTP requests to my API.</p>
</li>
<li><p><strong>SSH (port 22):</strong> Allowed me to connect to the server from my laptop for initial setup.</p>
</li>
</ul>
<p>After setting up AWS Systems Manager Session Manager, I removed the inbound SSH rule, so I no longer needed to expose port 22 for routine server access.</p>
<p>With my cloud server ready, I still needed a way for GitHub Actions to trigger deployments whenever a new version of my application was ready. That's where IAM, OIDC, and AWS Systems Manager came in.</p>
<h2>#6) Connecting GitHub Actions to AWS Securely</h2>
<p>I needed a secure way for GitHub Actions to communicate with AWS and trigger deployments on my EC2 instance. To set this up, I used <strong>IAM</strong> and <strong>OpenID Connect (OIDC)</strong>.</p>
<h3>Understanding IAM and IAM Roles</h3>
<p>IAM (Identity and Access Management) controls who can access AWS resources and what actions they can perform.</p>
<p>For example, GitHub Actions needed permission to request deployments on my EC2 instance, but it didn't need unrestricted access to my AWS account. I used IAM policies to define the actions it was allowed to perform.</p>
<p>An <strong>IAM role</strong> is an identity that a trusted entity can temporarily assume to obtain AWS permissions. A role has two important parts:</p>
<ul>
<li><p><strong>Trust policy:</strong> Defines who is allowed to assume the role.</p>
</li>
<li><p><strong>Permission policy:</strong> Defines what actions the role can perform after assuming it.</p>
</li>
</ul>
<h3>Using OIDC Instead of Permanent AWS Access Keys</h3>
<p>I could have stored permanent AWS access keys in GitHub repository secrets, but I wanted to avoid managing long-lived credentials for this pipeline. Instead, I used <strong>OpenID Connect (OIDC)</strong>.</p>
<p>OIDC allows GitHub Actions to prove its identity to AWS using a verifiable identity token. AWS checks the token against the role's trust policy before deciding whether to allow the workflow to assume the role.</p>
<p>Here's how I configured it:</p>
<ol>
<li><p>I configured AWS to trust GitHub's OIDC identity provider.</p>
</li>
<li><p>I created an IAM role whose trust policy restricts access to my specific repository and the <code>main</code> branch.</p>
</li>
<li><p>I attached a permission policy allowing the role to request deployment commands through AWS Systems Manager and check their execution results.</p>
</li>
</ol>
<p>When an eligible workflow runs, GitHub Actions requests an identity token. If AWS validates it and the trust conditions match, the workflow receives temporary AWS credentials and can perform only the actions allowed by its role.</p>
<p>This way, I didn't need to store permanent AWS access keys in GitHub Secrets.</p>
<p>GitHub Actions wasn't the only component that needed AWS permissions, though. My EC2 instance also needed its own IAM role to communicate with Systems Manager.</p>
<h2>#7) Using AWS Systems Manager to Deploy Without SSH</h2>
<p>With GitHub Actions authenticated with AWS, I still needed a way to send commands to my EC2 instance. For this, I used <strong>AWS Systems Manager</strong>, which helps manage and operate servers remotely.</p>
<p>Its <strong>Run Command</strong> feature lets us execute commands on a configured EC2 instance. This allowed GitHub Actions to request the commands needed to deploy my application without opening an SSH connection.</p>
<h3>What Does the SSM Agent Do?</h3>
<p>The <strong>SSM Agent</strong> is software that runs on EC2 and communicates with AWS Systems Manager. It executes the commands sent through Run Command and reports their status and results.</p>
<p>To enable this communication, I attached a separate IAM role to my EC2 instance. This gave it the permissions needed to communicate with Systems Manager.</p>
<p>I now had two IAM roles with different responsibilities:</p>
<ul>
<li><p><strong>GitHub Actions role:</strong> Allows the workflow to request deployment commands and check their results.</p>
</li>
<li><p><strong>EC2 instance role:</strong> Gives the instance the permissions needed to communicate with Systems Manager.</p>
</li>
</ul>
<h3>How GitHub Actions Reaches My EC2 Instance</h3>
<p>Here's how the process works:</p>
<ol>
<li><p>GitHub Actions authenticates with AWS through OIDC and assumes its IAM role.</p>
</li>
<li><p>The workflow asks Systems Manager to run deployment commands on EC2.</p>
</li>
<li><p>The SSM Agent executes the commands and reports their status through Systems Manager.</p>
</li>
<li><p>GitHub Actions checks the execution results to determine whether the commands succeeded.</p>
</li>
</ol>
<p>With this setup, I could automate deployments without relying on SSH. Next, I connected these pieces to my GitHub Actions deployment job to pull and run the new Docker image.</p>
<h2>#8) Automating the Deployment with GitHub Actions</h2>
<p>With the AWS setup ready, I connected everything to my GitHub Actions workflow to automate deployments.</p>
<p>I added a separate deployment job that runs after the Docker image is successfully published to GHCR. I configured it to deploy only when changes are pushed to <code>main</code>, rather than on every pull request.</p>
<h3>Authenticating and Triggering the Deployment</h3>
<p>I used the <code>aws-actions/configure-aws-credentials</code> action to authenticate with AWS using the IAM role and OIDC configuration from the previous sections. I also added the <code>id-token: write</code> permission so GitHub Actions could request an OIDC identity token and obtain temporary AWS credentials.</p>
<p>Next, I used the AWS CLI's <code>aws ssm send-command</code> command with the <code>AWS-RunShellScript</code> document to send deployment instructions to EC2 through Systems Manager, without needing an SSH connection.</p>
<h3>Giving EC2 Access to GHCR</h3>
<p>My GHCR package was private, so my EC2 instance needed to authenticate before it could pull the Docker image. The <code>GITHUB_TOKEN</code> I used in GitHub Actions to publish the image wasn't automatically available on EC2.</p>
<p>To handle this, I created a GitHub <strong>Personal Access Token (PAT)</strong> with the <code>read:packages</code> permission and used it to authenticate Docker with GHCR on my EC2 instance.</p>
<p>Since I ran Docker commands as root, Docker stored the registry credentials in <code>/root/.docker/config.json</code> on the EC2 instance. This allowed Docker to use the saved credentials when pulling the image during deployment, without requiring me to provide the token with every pull command.</p>
<p>One important thing to keep in mind is that Docker's default configuration doesn't encrypt these credentials. So, access to this file should be restricted, and the token should never be included in the Docker image, deployment logs, or source code. For production setups, using a secure credential store or secrets manager is a better approach.</p>
<h3>Updating the Running Container</h3>
<p>The deployment commands sent to EC2 handle three main steps:</p>
<ol>
<li><p>Pull the Docker image tagged with the current Git commit SHA from GHCR.</p>
</li>
<li><p>Stop and remove the existing application container.</p>
</li>
<li><p>Start a new container using the updated image, with the appropriate port mapping to make the API accessible.</p>
</li>
</ol>
<p>Using the commit SHA lets me identify the exact version being deployed instead of relying only on the <code>latest</code> tag.</p>
<p>Finally, the workflow checks the command's execution status through Systems Manager. Successfully submitting a command doesn't guarantee that it completed successfully, so checking its result is an important part of the deployment process.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6acb20b64616d9559a0b65ed/09e7b674-97d6-472c-a628-c2c036083cc1.png" alt="" style="display:block;margin:0 auto" />

<h2>#9) Making Sure My Deployment Actually Works</h2>
<p>At this point, GitHub Actions could trigger deployments on my EC2 instance. But there was still a problem: how could I be sure that the new version was actually working?</p>
<p>A Docker container starting successfully doesn't necessarily mean the application is working correctly. To verify each deployment, I added a health check using the <code>/health</code> endpoint in my Express API.</p>
<h3>Checking Whether the Application Is Healthy</h3>
<p>After starting the new container, the deployment process sends a request to <code>/health</code> and checks the response. If the API responds as expected, the deployment is considered successful. Otherwise, the workflow should report a failure.</p>
<h3>What If the New Version Fails?</h3>
<p>This is where <strong>rollback</strong> comes in. It means returning to a previously working version when a new deployment fails.</p>
<p>Since I tagged my Docker images with their Git commit SHA, I could identify the exact version to restore. If the new version fails its health check, the deployment logic attempts to stop the failed container and start the previous version again, while reporting the deployment as failed.</p>
<p>Rollback doesn't fix the underlying bug, but it helps restore the previous version while I investigate the issue.</p>
<p>With health checks and rollback, my pipeline could verify deployments and attempt to recover when something went wrong.</p>
<h2>The Complete CI/CD Pipeline</h2>
<p>We've covered GitHub Actions, Docker, GHCR, AWS EC2, IAM, OIDC, and Systems Manager. Here's how everything comes together when I push a change to my application.</p>
<h3>From a Git Push to a Running Application</h3>
<ol>
<li><p><strong>I push code to GitHub:</strong> Pushing changes to main triggers the workflow.</p>
</li>
<li><p><strong>GitHub Actions runs the tests:</strong> If the tests fail, the pipeline stops before publishing the image or proceeding with deployment.</p>
</li>
<li><p><strong>Docker builds and publishes the image:</strong> Once the tests pass, GitHub Actions builds the Docker image and pushes it to GHCR, tagging it with the Git commit SHA.</p>
</li>
<li><p><strong>GitHub Actions authenticates with AWS:</strong> The deployment job uses OIDC to obtain temporary AWS credentials by assuming the IAM role I configured, without storing permanent AWS access keys in GitHub Secrets.</p>
</li>
<li><p><strong>Systems Manager deploys the image to EC2:</strong> GitHub Actions sends deployment commands through Systems Manager, and the SSM Agent executes them on my EC2 instance. These commands pull the new image and replace the running container.</p>
</li>
<li><p><strong>The pipeline verifies the deployment:</strong> The deployment process checks the application's /health endpoint. If the check fails, the rollback logic attempts to restore the previous version.</p>
</li>
</ol>
<p>After the deployment succeeded, I verified that the application was accessible through my EC2 instance's public IP. The <code>/health</code> endpoint returned HTTP <code>200 OK</code> with a <code>{"status":"ok"}</code> response, confirming that the API was reachable from outside AWS.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6acb20b64616d9559a0b65ed/ec7296f6-5926-4f6e-9cd1-93ad272f34f5.png" alt="" style="display:block;margin:0 auto" />

<p>With that, my first end-to-end CI/CD pipeline was working! I no longer needed to build images manually, transfer them to a server, or SSH into EC2 for every deployment. The journey from a Git push to a running application was now automated.</p>
<h2>What I Learned While Building My CI/CD Pipeline</h2>
<p>My goal with this project wasn't to build a complex application, but to understand how code makes its way from GitHub to a running application on a server.</p>
<p>Building the pipeline helped me connect technologies I'd been learning separately, including GitHub Actions, Docker, AWS EC2, IAM, OIDC, and Systems Manager.</p>
<p>I learned that CI/CD isn't just about automating commands. It's also about managing permissions securely, checking whether deployments succeed, and recovering when something goes wrong.</p>
<p>There's still room to improve this setup, especially with zero-downtime deployments, HTTPS, and monitoring. But building the pipeline from scratch helped me understand how these pieces fit together in practice.</p>
<p>For me, the biggest takeaway wasn't just getting the application running on a server, but understanding what happens <strong>Under the Hood</strong> to get it there.</p>
]]></content:encoded></item><item><title><![CDATA[Understanding Linux Storage and Filesystems: From Disks to Files]]></title><description><![CDATA[Learn how Linux storage works, from disks and partitions to filesystems, mount points, UUIDs, and essential storage commands.]]></description><link>https://tanmayydev.hashnode.dev/understanding-linux-storage-and-filesystems</link><guid isPermaLink="true">https://tanmayydev.hashnode.dev/understanding-linux-storage-and-filesystems</guid><category><![CDATA[Linux]]></category><category><![CDATA[linux-file-system]]></category><category><![CDATA[linux-basics]]></category><category><![CDATA[linux for beginners]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Tanmay Vishwakarma]]></dc:creator><pubDate>Mon, 12 Oct 2026 14:14:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6acb20b64616d9559a0b65ed/932710cd-c920-4fd8-87fd-94fe8a5e099d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you ever wondered how Linux organizes your files and where they actually live on your storage drive?</p>
<p>Let's say you create a file called <code>app.py</code> and save it in your Projects folder. You can open it, edit it, rename it, or delete it whenever you want. But how does Linux organize this file? How does it connect the files and directories you see to the actual storage inside your computer?</p>
<p>There's more going on than you might think!</p>
<p>Linux uses a few different concepts to manage storage and organize data. Once you understand how they work together, things like disks, partitions, filesystems, and mount points will make a lot more sense.</p>
<p>In this blog, we'll break these concepts down step by step, using simple explanations and diagrams to understand what's happening behind the scenes. The goal is to build a clear mental picture of how Linux storage is organized, from the physical disk all the way to the files you use every day.</p>
<p>We'll focus on understanding the core concepts here. In future blogs, we'll get more practical and explore these topics using Linux commands and hands-on examples.</p>
<p>Let's start with the basics!</p>
<h2>#1) What Is Storage?</h2>
<p><strong>Storage is where your data is kept</strong>, even when you shut down your computer.</p>
<p>Your laptop might have an SSD or a hard disk drive (HDD) to store the operating system, applications, documents, projects, and other files.</p>
<p>For example, your laptop might have a 500 GB SSD:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6acb20b64616d9559a0b65ed/853d8569-e1b2-4450-b152-ca18330c078c.png" alt="" style="display:block;margin:0 auto" />

<p>The SSD is the physical storage device inside your computer. It provides the space where your data is kept.</p>
<p>But how does Linux identify and access this device?</p>
<h3># How does Linux represent storage devices?</h3>
<p>Linux represents storage devices through device files under the <code>/dev</code> directory.</p>
<p>For example, an NVMe SSD might appear as:</p>
<p><code>/dev/nvme0n1</code></p>
<p>A SATA or SCSI-style disk might appear as:</p>
<p><code>/dev/sda</code></p>
<p>These names are how Linux refers to the corresponding storage devices. The exact names depend on the type of device and your system's configuration.</p>
<p>For now, you don't need to memorize these names. Just remember that <code>/dev</code> contains device files through which Linux can access hardware and other devices.</p>
<p>Now that we know what a disk is and how Linux identifies it, let's look at how a single disk can be divided into smaller sections called <strong>partitions</strong>.</p>
<h2>#2) Disk vs Partition</h2>
<p>Now that we know what a disk is, let's understand partitions.</p>
<p>Imagine your laptop has a 500 GB SSD. You could use the entire disk as one storage area, but you can also divide it into smaller sections called <strong>partitions</strong>.</p>
<p>For example, here's a 500 GB disk:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6acb20b64616d9559a0b65ed/3fb1df27-e30c-44ab-a25f-76e85bd9b467.png" alt="" style="display:block;margin:0 auto" />

<p>You could divide this disk into three partitions:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6acb20b64616d9559a0b65ed/87841f61-d2c5-4287-bcd5-259c58a11018.png" alt="" style="display:block;margin:0 auto" />

<p>Each partition represents a defined region of the disk.</p>
<p>So, the relationship looks like this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6acb20b64616d9559a0b65ed/e25c2f24-6425-45c9-80a3-36a3f9323d7a.png" alt="" style="display:block;margin:0 auto" />

<p>Think of a disk as a large storage space and partitions as separate sections within it.</p>
<h3># Why do we need partitions?</h3>
<p>Partitions let us divide a disk into separate areas for different purposes.</p>
<p>For example, a Linux installation might use partitions like these:</p>
<ul>
<li><p><strong>EFI System Partition:</strong> Stores boot-related files used by UEFI firmware to start the operating system.</p>
</li>
<li><p><strong>Root partition (</strong><code>/</code><strong>):</strong> Stores the Linux system, installed software, configuration files, and other data within the root filesystem.</p>
</li>
<li><p><strong>Home partition (</strong><code>/home</code><strong>):</strong> Can be used to store users' personal files and settings separately from the root filesystem.</p>
</li>
<li><p><strong>Swap partition:</strong> Can provide disk space for Linux swap.</p>
</li>
</ul>
<p>These are common uses, not a list of partitions that every Linux system must have. For example, <code>/home</code> can simply be a directory inside the root filesystem instead of having its own partition. Swap can also be configured as a file rather than a partition.</p>
<p>One more thing worth remembering: <strong>a partition is not the same as a filesystem.</strong> A partition defines a region of storage, while a filesystem organizes data within that region. We'll look at filesystems next!</p>
<h2>#3) What Is a Filesystem?</h2>
<p>So far, we've learned that a disk can be divided into partitions. But here's a question: <strong>how does Linux organize data inside a partition?</strong></p>
<p>Imagine you have a partition with 100 GB of storage. It provides space to store data, but we still need a way to organize that data into files and directories.</p>
<p>For example, how would Linux keep track of a file named <code>project.py</code>, its contents, who owns it, and which directory it belongs to?</p>
<p>That's where a <strong>filesystem</strong> comes in.</p>
<h3># What does a filesystem do?</h3>
<p>A filesystem is a method of organizing and managing data on a storage device. It keeps track of things like:</p>
<ul>
<li><p><strong>Files and directories:</strong> How files are organized and how they relate to each other.</p>
</li>
<li><p><strong>Metadata:</strong> Information about files, such as their size, timestamps, ownership, and permissions.</p>
</li>
<li><p><strong>Data locations:</strong> Where the contents of a file are stored within the filesystem.</p>
</li>
</ul>
<p>Without a filesystem, Linux wouldn't have the familiar structure of files and directories that we use every day.</p>
<p>Think of a filesystem as the system that organizes storage and makes it possible to work with data as files and directories rather than having to manage raw storage blocks yourself.</p>
<h3># Common Linux filesystems</h3>
<p>Linux supports several filesystem types, each with its own features. Here are some common examples:</p>
<ul>
<li><p><strong>ext4:</strong> A widely used filesystem on Linux desktops and servers.</p>
</li>
<li><p><strong>XFS:</strong> A filesystem commonly used on servers, including systems that work with large files and large amounts of data.</p>
</li>
<li><p><strong>Btrfs:</strong> A filesystem that supports features such as snapshots, checksums, and compression.</p>
</li>
<li><p><strong>ZFS:</strong> A storage and filesystem system known for features such as data integrity checks, snapshots, and storage pooling.</p>
</li>
<li><p><strong>tmpfs:</strong> A temporary filesystem that stores data primarily in memory, rather than on a regular disk-based filesystem.</p>
</li>
</ul>
<h3># How does a filesystem fit into our storage setup?</h3>
<p>Let's return to our earlier example. Suppose you have a partition named <code>/dev/nvme0n1p3</code>.</p>
<p>You can create an ext4 filesystem on that partition. Once the filesystem is created, it can organize data into files and directories.</p>
<p>For example:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6acb20b64616d9559a0b65ed/15066b2f-bf5f-408f-97cf-1226569ef297.png" alt="" style="display:block;margin:0 auto" />

<p>Creating a filesystem is commonly called <strong>formatting</strong> a partition. Be careful, though, creating a new filesystem can destroy data that was previously stored there.</p>
<p>One important thing to remember is that a filesystem doesn't have to be created on a partition specifically. It can also be created on other suitable storage devices, such as a logical volume. We'll keep our examples focused on partitions for now.</p>
<p>Now that we understand disks, partitions, and filesystems individually, let's put them together and see how they fit into the same storage hierarchy.</p>
<h2>#4) Disk → Partition → Filesystem</h2>
<p>Let's put everything together.</p>
<p>Imagine your SSD is divided into partitions, and one of those partitions has an ext4 filesystem.</p>
<p>The relationship looks like this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6acb20b64616d9559a0b65ed/24c65bab-c2d6-4f06-ac8f-99447cb066e3.png" alt="" style="display:block;margin:0 auto" />

<p>Each layer has a different purpose:</p>
<ul>
<li><p><strong>Disk:</strong> Provides physical storage.</p>
</li>
<li><p><strong>Partition:</strong> Defines a region of the disk.</p>
</li>
<li><p><strong>Filesystem:</strong> Organizes data into files and directories.</p>
</li>
</ul>
<p>Remember, a partition is not the same as a filesystem. A partition defines a region of storage, while a filesystem organizes data within it. This is a common setup, though filesystems can also be created on other suitable storage devices.</p>
<p>Now, how do we access these files through the Linux directory structure? Let's understand <strong>mounting</strong>.</p>
<h2>#5) What Is Mounting?</h2>
<p>Unlike Windows, which commonly uses drive letters like <code>C:</code> and <code>D:</code>, Linux makes filesystems accessible through a single directory tree starting at <code>/</code> (the root directory).</p>
<p><strong>Mounting</strong> means attaching a filesystem to a directory in this tree so you can access its files. This directory is called a mount point.</p>
<p>For example, suppose <code>/dev/nvme0n1p3</code> contains an ext4 filesystem. You could mount it at <code>/mnt/data</code>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6acb20b64616d9559a0b65ed/b7b56d39-c739-4a71-b8b0-b1d7ba70c1d7.png" alt="" style="display:block;margin:0 auto" />

<p>A filesystem can be mounted at different locations, such as <code>/mnt/data</code>, <code>/home</code>, or <code>/opt/storage</code>. This is possible because Linux uses a single directory tree instead of assigning a separate drive letter to each filesystem.</p>
<p>One thing to remember: mounting a filesystem over a directory hides that directory's existing contents until the filesystem is unmounted. The files aren't deleted.</p>
<h2>#6) Disk vs Partition vs Filesystem vs Mount Point</h2>
<p>These four terms are closely related, but each has a different purpose.</p>
<table>
<thead>
<tr>
<th>Concept</th>
<th>What it means</th>
</tr>
</thead>
<tbody><tr>
<td>Disk</td>
<td>The physical storage device, such as an SSD or HDD, that provides space to store data.</td>
</tr>
<tr>
<td>Partition</td>
<td>A defined region of a disk that can be used for a specific purpose, such as storing a filesystem or providing swap space.</td>
</tr>
<tr>
<td>Filesystem</td>
<td>A structure that organizes and manages data on a storage device, keeping track of files, directories, metadata, and file contents.</td>
</tr>
<tr>
<td>Mount point</td>
<td>A directory in Linux's directory tree where a filesystem is attached, making its files and directories accessible from that location.</td>
</tr>
</tbody></table>
<p>One thing to remember is that this is a common storage setup, but not the only possibility. A filesystem can also be created directly on a whole disk or another suitable block device without creating a partition first.</p>
<h2>#7) UUID and <code>/etc/fstab</code></h2>
<p>When you mount a filesystem manually using the <code>mount</code> command, it usually stays mounted until you unmount it or restart your computer. So, how does Linux know which filesystems to mount automatically when the system starts?</p>
<p>That's where <code>/etc/fstab</code> comes in.</p>
<h3># What is <code>/etc/fstab</code>?</h3>
<p><code>/etc/fstab</code> is a configuration file that tells Linux which filesystems to mount, where to mount them, and which options to use.</p>
<p>For example, an entry might look like this:</p>
<pre><code class="language-text">UUID=xxxx-xxxx  /mnt/data  ext4  defaults  0  2
</code></pre>
<p>Here's what the fields mean:</p>
<ul>
<li><p><code>UUID=xxxx-xxxx</code><strong>:</strong> Identifies the filesystem.</p>
</li>
<li><p><code>/mnt/data</code><strong>:</strong> The mount point.</p>
</li>
<li><p><code>ext4</code><strong>:</strong> The filesystem type.</p>
</li>
<li><p><code>defaults</code><strong>:</strong> The mount options to use.</p>
</li>
<li><p><code>0 2</code><strong>:</strong> Fields related to the legacy dump utility and filesystem-check order.</p>
</li>
</ul>
<p>An entry in <code>/etc/fstab</code> allows Linux to mount the filesystem automatically at boot, instead of requiring you to mount it manually each time.</p>
<h3># What is a UUID?</h3>
<p>A <strong>UUID (Universally Unique Identifier)</strong> is an identifier assigned to a filesystem.</p>
<p>Instead of referring to a device by a name like <code>/dev/sdb1</code>, an <code>/etc/fstab</code> entry can use its UUID. This is useful because device names can change depending on how storage devices are detected.</p>
<p>In simple terms, <strong>a UUID identifies the filesystem, while</strong> <code>/etc/fstab</code> <strong>tells Linux where and how to mount it.</strong></p>
<h2>#8) The Complete Linux Storage Hierarchy</h2>
<p>We've covered quite a few concepts, so let's put everything together and see how they fit into a typical Linux setup.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6acb20b64616d9559a0b65ed/3ce2e317-2f91-4bd1-accf-175be8e9933f.png" alt="" style="display:block;margin:0 auto" />

<p>In this example, an ext4 filesystem is created on the partition <code>/dev/nvme0n1p2</code> and mounted at <code>/</code>, the root of the Linux directory tree.</p>
<p>Directories such as <code>/etc</code>, <code>/home</code>, and <code>/var</code> are accessible through this tree. They belong to the root filesystem unless separate filesystems are mounted at those locations.</p>
<p>So, when you access <code>/home/tanmay/Projects/app.py</code>, you're accessing a file through the Linux directory tree, backed by the filesystem mounted at <code>/</code> in this example.</p>
<h2>#9 Exploring storage on your Linux system</h2>
<p>Now that we understand the concepts, let's look at a few commands that can help us explore storage on our own Linux systems.</p>
<ul>
<li><p><code>lsblk</code> - Displays block devices in a tree-like structure, showing disks and their partitions.</p>
</li>
<li><p><code>lsblk -f</code> - Shows filesystem types, UUIDs, labels, and mount points.</p>
</li>
<li><p><code>findmnt</code> - Shows which filesystems are currently mounted and where they are mounted.</p>
</li>
<li><p><code>df -hT</code> - Shows filesystem types, total space, used space, and available space in a human-readable format.</p>
</li>
<li><p><code>du -sh ~/Projects</code> - Shows the disk space used by your Projects directory. Replace the path with a directory on your own system.</p>
</li>
<li><p><code>cat /etc/fstab</code> - Displays the configuration for filesystems and other storage resources that Linux can mount or activate, including entries configured for automatic mounting at boot.</p>
</li>
</ul>
<p>You don't need to memorize all these commands right away. The goal is to understand what information each one provides and how it connects to the concepts we've covered.</p>
<h2># And That's the Big Picture!</h2>
<p>Linux storage might seem complicated at first, but its basic concepts become much easier to understand when we break them down step by step.</p>
<p>A disk provides the physical storage, partitions divide it into regions, and filesystems organize data into files and directories. Mount points connect those filesystems to Linux's directory tree, while UUIDs and <code>/etc/fstab</code> help identify and configure filesystems for automatic mounting.</p>
<p>Now that we've covered these fundamentals, we have a solid starting point for exploring more advanced Linux storage topics.</p>
<p>In future blogs, we'll explore these concepts further with practical examples and Linux commands, including <strong>Logical Volume Management (LVM)</strong> and how it makes storage management more flexible.</p>
<p><strong>This is just the beginning.</strong></p>
<p><strong>There's a lot more to explore under the hood of Linux!</strong></p>
]]></content:encoded></item></channel></rss>