<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: utkarsh srivastava</title>
    <description>The latest articles on DEV Community by utkarsh srivastava (@utkarsh4321).</description>
    <link>https://dev.to/utkarsh4321</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F291905%2Ff78b992f-3fe2-4273-9f3c-6ca14e220165.jpeg</url>
      <title>DEV Community: utkarsh srivastava</title>
      <link>https://dev.to/utkarsh4321</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/utkarsh4321"/>
    <language>en</language>
    <item>
      <title>How to Containerize a Full-Stack App (Part 2)-Optimizing the Frontend with Multi-Stage Docker Builds.</title>
      <dc:creator>utkarsh srivastava</dc:creator>
      <pubDate>Wed, 04 Nov 2026 03:28:21 +0000</pubDate>
      <link>https://dev.to/utkarsh4321/how-to-containerize-a-full-stack-app-part-2-optimizing-the-frontend-with-multi-stage-docker-cg9</link>
      <guid>https://dev.to/utkarsh4321/how-to-containerize-a-full-stack-app-part-2-optimizing-the-frontend-with-multi-stage-docker-cg9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Series Navigation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Part 1:&lt;/strong&gt; &lt;a href="https://dev.to/utkarsh4321/how-to-containerize-a-full-stack-app-part-1-setting-up-the-local-development-environment-18ee"&gt;&lt;strong&gt;Setting up locally&lt;/strong&gt;&lt;/a&gt; &lt;em&gt;(Required)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;📍 &lt;strong&gt;Part 2:&lt;/strong&gt; &lt;strong&gt;Frontend Multi-Stage Docker Builds&lt;/strong&gt; &lt;em&gt;(You are here)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;➡️ &lt;strong&gt;Part 3:&lt;/strong&gt; &lt;a href="http://dev.to/utkarsh4321/how-to-containerize-a-full-stack-app-part-3-containerizing-the-backend-and-mysql-database-3kc8"&gt;&lt;strong&gt;Containerizing the Backend &amp;amp; MySQL&lt;/strong&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Intro
&lt;/h4&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/hgDQK3Z0FSU" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;Part 2&lt;/strong&gt; of our series. If you haven't set up the local Full-Stack Todo app yet, check out &lt;a href="https://dev.to/utkarsh4321/part1-how-to-containerize-a-full-stack-app-react-express-and-mysql-4coi-temp-slug-660007/edit"&gt;&lt;strong&gt;Part 1: Local Setup&lt;/strong&gt;&lt;/a&gt; to get your environment ready.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Part 2&lt;/strong&gt;, we will guide you on how to containerize our &lt;strong&gt;Frontend&lt;/strong&gt; application. In the upcoming blog post, I will explain the rest of the details.&lt;/p&gt;

&lt;p&gt;If you are a frontend developer and only want to know how to Dockerize your frontend, this section is very important. Also, you can use this Docker image in production as well.&lt;/p&gt;

&lt;p&gt;Note: This Setup you can use for any &lt;strong&gt;React&lt;/strong&gt; app that uses Vite. There may be a few changes if you are using some other bundler. But the basic idea of deployment remains the same.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here is what we will cover:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Frontend Code Walkthrough:&lt;/strong&gt; A brief overview of our React app structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generating the Production Build:&lt;/strong&gt; How to prepare the app for deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment Architecture:&lt;/strong&gt; High-level view of our deployment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;From Source Code to Container:&lt;/strong&gt; Visualizing the transformation process&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writing Our First Dockerfile:&lt;/strong&gt; Writing the step-by-step build instructions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creating nginx configuration file:&lt;/strong&gt; Configuring nginx to serve our static files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Building Docker Image:&lt;/strong&gt; Building Docker Image and Running it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managing Environment Variables:&lt;/strong&gt; Configuring API URLs and secrets securely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker File optimization:&lt;/strong&gt; Intro to Docker Image Optimization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Stage Docker file:&lt;/strong&gt; The professional way to shrink image size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Securing Frontend Docker Container:&lt;/strong&gt; Restricting folder permissions and user access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bonus:&lt;/strong&gt; Extra observation on nginx.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wrap Up&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Frontend Code Walkthrough:&lt;/strong&gt; A brief overview of our React app structure.&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/GJXg9DXrFUw" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Our React app was built via &lt;code&gt;vite&lt;/code&gt;. It had a folder structure like below&lt;/p&gt;

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

&lt;p&gt;Where&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;frontend/
├── public/                 # Static assets (favicons, manifest, etc.)
├── src/                    # our todo  application source code
|——— .env                   # environment file    
├── .env-example            # Template for environment variables
├── .gitignore              # Files/folders to be ignored by Git
├── eslint.config.js        # Linting rules to keep code consistent
├── index.html              # Entry Point. Vite treats this file as source code. It contains a &amp;lt;script type="module" src="..."&amp;gt; tag that points to your React entry file (usually src/main.tsx).
├── package-lock.json       # Exact versions of installed dependencies
├── package.json            # Project metadata and script definitions
├── README.md               # Project documentation
├── tsconfig.app.json       # TypeScript config for the frontend app
├── tsconfig.json           # Main TypeScript configuration
├── tsconfig.node.json      # TypeScript config for Node-based tools (like Vite)
└── vite.config.ts          # Vite-specific settings and plugins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. &lt;strong&gt;Generating the Production Build:&lt;/strong&gt; How to prepare the app for deployment.&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/xQfngr_QYoE" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;So, to deploy this React app. We first need to create a bundle of it by running the build command. &lt;/p&gt;

&lt;p&gt;So we need to run&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Once you run this command inside the &lt;strong&gt;frontend&lt;/strong&gt; folder.&lt;br&gt;
You can see the &lt;strong&gt;dist&lt;/strong&gt; folder, which looks like the following structure&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; dist/
 ├── assets/
  │   ├── index-DiW657_O.css # All your CSS (minified)
  │   └── index-D9fK2_zX.js  # All your React code + libraries (minified)
 ├── index.html # The main entry point (minified)
 └── vite.svg # Copied directly from public/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By running this command, Vite took our development code and transformed it into a highly optimized bundle designed for web browsers. Because our browser does not understand JSX or TypeScript. So Vite converts our code into standard JavaScript.&lt;/p&gt;

&lt;p&gt;Vite creates a new directory in our root folder called &lt;strong&gt;dist/&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;This folder contains everything your web server needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;index.html:&lt;/strong&gt; The entry point, now updated with the correct paths to your hashed JS and CSS files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;assets/:&lt;/strong&gt; A folder containing your minified &lt;code&gt;.js&lt;/code&gt; and &lt;code&gt;.css&lt;/code&gt; files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now to test our build locally, run the command&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;This starts a local server that serves the dist folder, allowing you to see exactly how your app will perform in production.&lt;/p&gt;

&lt;p&gt;Now go to your browser and type &lt;code&gt;http://localhost:4173/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You are able to see our todo app, so it shows that our build was created successfully, and it is also running perfectly.&lt;/p&gt;

&lt;p&gt;Now kill that server by running &lt;code&gt;ctrl + c&lt;/code&gt; or &lt;code&gt;cmd + c&lt;/code&gt; in mac.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Deployment Architecture:&lt;/strong&gt; High-level view of our React app deployment.&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/h5Fee_wmvuw" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Before we proceed, we first need to understand how our &lt;strong&gt;React&lt;/strong&gt; app actually runs in production.&lt;/p&gt;

&lt;p&gt;So in the code walkthrough section, we ran the &lt;code&gt;npm run build&lt;/code&gt; command in order to build our project. Then in order to test that build&lt;br&gt;
Locally, we ran &lt;code&gt;npm run preview&lt;/code&gt; command. This command spins up a &lt;code&gt;web server&lt;/code&gt; and serves the &lt;strong&gt;dist&lt;/strong&gt; folder.&lt;/p&gt;

&lt;p&gt;We need to set up a similar environment for our production.&lt;/p&gt;

&lt;p&gt;For the web server, we will use nginx because it is mostly used in production. And nginx will handle upcoming requests and serve the dist folder.&lt;/p&gt;

&lt;p&gt;See the diagram to get a better idea&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;In summary:&lt;/strong&gt; Nginx takes a web request, finds the correct static file on the disk, and hands it back to the browser.&lt;/p&gt;

&lt;p&gt;For Production, we need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A web server, i.e., nginx&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;dist&lt;/strong&gt; folder (bundle of our application)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  4. &lt;strong&gt;From Source Code to Container:&lt;/strong&gt; Visualizing the transformation process.&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/4jDCiaHV4Hc" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Before we proceed, I would like to provide a high-level overview of containerization. &lt;/p&gt;

&lt;p&gt;Containerization involves a few steps&lt;/p&gt;

&lt;p&gt;Stage 1: Our Source Code&lt;br&gt;
Stage 2: Take our source code and compile our source code and configuration into a package&lt;br&gt;
Stage 3: The result of &lt;strong&gt;Stage 2&lt;/strong&gt; is a static &lt;strong&gt;Docker Image&lt;/strong&gt;&lt;br&gt;
Stage 4: This is the live, running instance of the created Docker image.  Now we can access our application&lt;/p&gt;

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

&lt;p&gt;I hope the above diagram gives us some idea.&lt;/p&gt;

&lt;p&gt;So I think our responsibility is to create a Docker image for our application.&lt;/p&gt;

&lt;p&gt;But how do we provide our source code to Docker so that it can build the Docker image?&lt;/p&gt;

&lt;p&gt;Also, how does it understand what it needs to do to build our project?&lt;/p&gt;

&lt;p&gt;So here comes &lt;code&gt;Dockerfile&lt;/code&gt; to rescue us. &lt;/p&gt;
&lt;h4&gt;
  
  
  4.1 What is a Dockerfile?
&lt;/h4&gt;

&lt;p&gt;"A Dockerfile is a simple text file that acts as a blueprint for building a Docker Image. It contains a set of sequential instructions that tell Docker exactly how to set up the environment, copy your code, and get your application running."&lt;/p&gt;

&lt;p&gt;We need to create a Dockerfile in our frontend directory. In the Dockerfile, we define everything that is required to build our project.&lt;/p&gt;

&lt;p&gt;So, how do we create our Docker file, and what content will we put inside it so that Docker understands it and create docker image by reading it?&lt;/p&gt;
&lt;h3&gt;
  
  
  5. &lt;strong&gt;Writing Our First Dockerfile:&lt;/strong&gt; Writing the step-by-step build instructions&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/8lUbTWXxKKU" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Go to the frontend directory if you are not and inside that directory, create a file with the name &lt;strong&gt;Dockerfile&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Open Dockerfile.&lt;/p&gt;

&lt;p&gt;So to build our project, we need npm (Node Package Manager)&lt;br&gt;
And it comes with Node.js. So first, we need Node.js &lt;/p&gt;

&lt;p&gt;So we will use a prebuilt Node.js image from Docker Hub. So that we can run the npm command.&lt;/p&gt;

&lt;p&gt;Copy the line in your Dockerfile&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;FROM node:lts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we know, to serve our React app, we need a &lt;strong&gt;web server&lt;/strong&gt;.&lt;br&gt;
We already discussed that we will use &lt;strong&gt;Nginx&lt;/strong&gt; for this.&lt;br&gt;
Now let's install nginx.&lt;/p&gt;

&lt;p&gt;Copy the line below in your &lt;strong&gt;Dockerfile&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Updates the Linux package manager and installs Nginx&lt;/span&gt;
&lt;span class="s"&gt;RUN apt-get update &amp;amp;&amp;amp; \&lt;/span&gt;
    &lt;span class="s"&gt;apt-get install -y nginx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's set up &lt;code&gt;WORKDIR&lt;/code&gt; inside our container. All subsequent commands (COPY, RUN, CMD) will be executed relative to this path.&lt;/p&gt;

&lt;p&gt;Copy the line below in your &lt;strong&gt;Dockerfile&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;WORKDIR /frontend-app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So let's recap what we've done. We have installed Node.js. Because we need it to build our project. Also, we installed nginx so that we can serve our React app.&lt;/p&gt;

&lt;p&gt;Now to serve our frontend. We need to build our ReactJS project. To build it, we first need to install the project dependencies. Then we need to copy our project code inside the container. So that when we run the &lt;strong&gt;npm run build&lt;/strong&gt; command. It successfully create build for our react.js project.&lt;/p&gt;

&lt;p&gt;For installing dependencies we need &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Add this line to your Dockerfile&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;COPY package*.json ./&lt;/span&gt;
&lt;span class="s"&gt;RUN npm ci&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will copy &lt;strong&gt;package.json&lt;/strong&gt;. Inside the workdir that we set up above. And then install dependencies.&lt;/p&gt;

&lt;p&gt;What is &lt;strong&gt;RUN npm ci&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Runs a "clean install." This is faster and more reliable than npm install for automated environments (CI/CD) because it adheres strictly to the package-lock.json.&lt;/p&gt;

&lt;p&gt;Via the above command, we installed our Project dependencies. Now we need to build our project. So, for building the project, we need to copy our frontend project code as well inside the container.&lt;/p&gt;

&lt;p&gt;Add this line to your Dockerfile&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;COPY . .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the rest of your source code into the container. Except files which are defined inside the &lt;strong&gt;dockerignore&lt;/strong&gt; file. We will talk about the &lt;strong&gt;dockerignore&lt;/strong&gt; later. &lt;/p&gt;

&lt;p&gt;Now, let's add a build command for building our project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;RUN npm run build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Via this command, we build our project. &lt;/p&gt;

&lt;p&gt;Now we need to set up our Nginx so that it serves our static build files.&lt;/p&gt;

&lt;p&gt;First of all, we create a directory. Where will we store our static files? Right now, our bundle files are present inside the &lt;strong&gt;dist&lt;/strong&gt; folder. &lt;/p&gt;

&lt;p&gt;Then we copy those files from &lt;strong&gt;dist&lt;/strong&gt; to the &lt;strong&gt;mywebsite&lt;/strong&gt; folder.&lt;/p&gt;

&lt;p&gt;Add these lines to your &lt;strong&gt;Dockerfile&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Ensure nginx directories exist&lt;/span&gt;
&lt;span class="s"&gt;RUN mkdir -p /frontend-app/mywebsite&lt;/span&gt;

&lt;span class="c1"&gt;# Copy nginx config&lt;/span&gt;
&lt;span class="s"&gt;COPY nginx.conf /etc/nginx/sites-available/default&lt;/span&gt;

&lt;span class="c1"&gt;# Move dist files&lt;/span&gt;
&lt;span class="s"&gt;RUN cp -r ./dist/* /frontend-app/mywebsite/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We did not talk about this line &lt;code&gt;COPY nginx.conf /etc/nginx/sites-available/default&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This line will overwrite the default Nginx configuration with your custom config file. This tells Nginx how to handle routing and where to look for static files.&lt;/p&gt;

&lt;p&gt;We will talk about it more later.&lt;/p&gt;

&lt;p&gt;We're done with our project setup. Now we need to expose the port as well. So that when we try to connect with our frontend, we can reach our web server, which is running on port 80.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;EXPOSE &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command tells users (and Docker) that the container listens on port 80. Here we are exposing the container port 80.&lt;/p&gt;

&lt;p&gt;Now let's start our &lt;strong&gt;nginx&lt;/strong&gt; server&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;CMD ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above command is divided into two commands:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nginx&lt;/code&gt;: Starts the &lt;strong&gt;nginx&lt;/strong&gt; server.&lt;br&gt;
&lt;code&gt;-g "daemon off;”&lt;/code&gt;: Runs Nginx in the foreground. If you don't do this, Nginx runs in the background, and the Docker container will immediately exit (stop) because it thinks its job is done.&lt;/p&gt;

&lt;p&gt;That's it, we're done with our Dockerfile. Final version of your Dockerfile looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;FROM node:lts&lt;/span&gt;

&lt;span class="c1"&gt;# Install nginx on Debian&lt;/span&gt;
&lt;span class="s"&gt;RUN apt-get update &amp;amp;&amp;amp; \&lt;/span&gt;
    &lt;span class="s"&gt;apt-get install -y nginx&lt;/span&gt;

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

&lt;span class="s"&gt;COPY package*.json ./&lt;/span&gt;
&lt;span class="s"&gt;RUN npm ci&lt;/span&gt;

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

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

&lt;span class="c1"&gt;# Ensure nginx directories exist&lt;/span&gt;
&lt;span class="s"&gt;RUN mkdir -p /frontend-app/mywebsite&lt;/span&gt;

&lt;span class="c1"&gt;# Copy nginx config&lt;/span&gt;
&lt;span class="s"&gt;COPY nginx.conf /etc/nginx/sites-available/default&lt;/span&gt;

&lt;span class="c1"&gt;# Move dist files&lt;/span&gt;
&lt;span class="s"&gt;RUN cp -r ./dist/* /frontend-app/mywebsite/&lt;/span&gt;

&lt;span class="s"&gt;EXPOSE &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;
&lt;span class="s"&gt;CMD ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. &lt;strong&gt;Creating nginx configuration file:&lt;/strong&gt; Configuring nginx to serve our static files&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/IRIhIEKHXy8" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;When we write our &lt;strong&gt;Dockerfile&lt;/strong&gt;. We encounter one line&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;COPY nginx.conf /etc/nginx/sites-available/default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are copying some &lt;strong&gt;nginx.conf&lt;/strong&gt; file to some location. But currently, in our project, inside the frontend directory. We don't have any nginx.conf file. So what's the purpose of this file?&lt;/p&gt;

&lt;p&gt;Let's talk about it&lt;/p&gt;

&lt;p&gt;Nginx comes with a default configuration file. When a request reaches Nginx, it looks for a configuration file which matches the request. If it cannot find the conf file, it selects &lt;code&gt;default.conf&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;And &lt;code&gt;default.conf&lt;/code&gt; comes with predefined rules. We want to override it with our custom configuration rules. So that niginx handle our request flawlessly.&lt;/p&gt;

&lt;p&gt;Also, we want nginx to look into &lt;strong&gt;/frontend-app/mywebsite&lt;/strong&gt; directory. For serving &lt;code&gt;static&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Create a file &lt;strong&gt;nginx.conf&lt;/strong&gt; inside frontend directory.&lt;/p&gt;

&lt;p&gt;And then copy and paste the content below into it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;server&lt;/span&gt; {
    &lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;;          &lt;span class="c"&gt;# Listen for IPv4
&lt;/span&gt;    &lt;span class="n"&gt;listen&lt;/span&gt; [::]:&lt;span class="m"&gt;80&lt;/span&gt;;     &lt;span class="c"&gt;# Listen for IPv6
&lt;/span&gt;    &lt;span class="n"&gt;server_name&lt;/span&gt; &lt;span class="n"&gt;localhost&lt;/span&gt;;

    &lt;span class="n"&gt;root&lt;/span&gt; /&lt;span class="n"&gt;frontend&lt;/span&gt;-&lt;span class="n"&gt;app&lt;/span&gt;/&lt;span class="n"&gt;mywebsite&lt;/span&gt;;
    &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt;;


    &lt;span class="n"&gt;location&lt;/span&gt; / {
        &lt;span class="n"&gt;try_files&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt; /&lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt;;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down each line step by step&lt;/p&gt;

&lt;p&gt;First things first, the conf file had &lt;/p&gt;

&lt;p&gt;server {&lt;br&gt;
  options&lt;br&gt;
}&lt;br&gt;
This denotes a server block. When any request reaches the web server, it will start looking for &lt;strong&gt;server_name&lt;/strong&gt; on these server blocks. Once &lt;strong&gt;server_name&lt;/strong&gt; matches, then that particular server block option is applied to the server.&lt;/p&gt;

&lt;p&gt;Nginx is designed to always serve something rather than just crashing. If it can't find a server_name that matches the URL the user typed, it looks for a block labelled with the default_server flag.&lt;/p&gt;

&lt;p&gt;Standard Default: Usually, this is a block that listens on Port 80 and points to a folder like /var/www/html or /usr/share/nginx/html.&lt;/p&gt;

&lt;p&gt;The Result: The user will see the famous "Welcome to nginx!" landing page instead of your actual website.&lt;/p&gt;

&lt;p&gt;I hope you understand the point. Now let's break down the above file step by step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;listen 80;&lt;/strong&gt;&lt;br&gt;
Tells Nginx to listen for incoming web traffic on port 80 using IPv4.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;listen [::]:80;&lt;/strong&gt;&lt;br&gt;
Tells Nginx to listen for incoming web traffic on port 80 using IPv6.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;server_name localhost;&lt;/strong&gt;&lt;br&gt;
Tells Nginx to apply these rules when the request comes to "localhost" (or your domain name in production).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;root /frontend-app/mywebsite;&lt;/strong&gt;&lt;br&gt;
Sets the home base. It tells Nginx that all your static files (HTML, CSS, JS) are stored inside the /frontend-app/mywebsite folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;index index.html;&lt;/strong&gt;&lt;br&gt;
Defines the default file. When a user visits your site without specifying a file, Nginx automatically serves them index.html.&lt;/p&gt;

&lt;p&gt;The "Magic" for Single Page Apps (React):&lt;/p&gt;

&lt;p&gt;location / {&lt;br&gt;
        try_files $uri /index.html;  This line prevents 404 errors on refresh. It tells Nginx: "First, look for the specific file the user asked for. If you can't find it, don't show an error—just serve index.html and let the JavaScript app handle the routing."&lt;br&gt;
    }&lt;/p&gt;

&lt;p&gt;$uri —&amp;gt; Requested File like if user type &lt;br&gt;
&lt;code&gt;http://localhost:8080/dashboard —&amp;gt; $uri is /dashboard.&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;Nginx checks: Does a file called &lt;code&gt;/frontend-app/mywebsite/dashboard&lt;/code&gt; exist?&lt;br&gt;
Result:&lt;br&gt;
No (Because /dashboard is just a virtual route in your React app, not a real file on the disk)&lt;/p&gt;

&lt;p&gt;Fallback: Nginx moves to the second option in your &lt;code&gt;try_files line -&amp;gt; /index.html.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Outcome: The user gets &lt;strong&gt;index.html&lt;/strong&gt;, and your JavaScript app loads and reads the URL to display the “Dashboard” page content.&lt;/p&gt;

&lt;p&gt;We are still not ready to build our image. Because if you try to build an image now. We will accidentally send our &lt;strong&gt;.env&lt;/strong&gt; secrets or a heavy folder like &lt;strong&gt;node_modules&lt;/strong&gt;. Because in our DockerFile we added this line &lt;strong&gt;COPY . .&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;We need to restrict this behaviour somehow so that Docker will not pick up those files. &lt;/p&gt;

&lt;p&gt;How can we do this?&lt;/p&gt;

&lt;p&gt;Ans is the &lt;strong&gt;.dockerignore&lt;/strong&gt; file.&lt;/p&gt;
&lt;h4&gt;
  
  
  What is .dockerignore?
&lt;/h4&gt;

&lt;p&gt;The &lt;strong&gt;.dockerignore&lt;/strong&gt; file is just like &lt;strong&gt;.gitignore&lt;/strong&gt;, but for Docker. It tells Docker which files to exclude when sending your code to the container builder.&lt;/p&gt;

&lt;p&gt;It speeds up the process of building images. It ignore heavy folder likes &lt;strong&gt;node_module&lt;/strong&gt;, &lt;strong&gt;dist&lt;/strong&gt; etc&lt;/p&gt;

&lt;p&gt;It prevents sensitive files (like &lt;strong&gt;.env&lt;/strong&gt; secrets or &lt;strong&gt;.git&lt;/strong&gt; history) from accidentally ending up inside your public image.&lt;/p&gt;

&lt;p&gt;Create a &lt;strong&gt;.dockerignore&lt;/strong&gt; file inside the frontend directory&lt;/p&gt;

&lt;p&gt;and copy the content&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;**&lt;/span&gt;/.classpath
&lt;span class="k"&gt;**&lt;/span&gt;/.dockerignore
&lt;span class="k"&gt;**&lt;/span&gt;/.env
&lt;span class="k"&gt;**&lt;/span&gt;/.git
&lt;span class="k"&gt;**&lt;/span&gt;/.gitignore
&lt;span class="k"&gt;**&lt;/span&gt;/.project
&lt;span class="k"&gt;**&lt;/span&gt;/.settings
&lt;span class="k"&gt;**&lt;/span&gt;/.toolstarget
&lt;span class="k"&gt;**&lt;/span&gt;/.vs
&lt;span class="k"&gt;**&lt;/span&gt;/.vscode
&lt;span class="k"&gt;**&lt;/span&gt;/.next
&lt;span class="k"&gt;**&lt;/span&gt;/.cache
&lt;span class="k"&gt;**&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;.&lt;span class="k"&gt;*&lt;/span&gt;proj.user
&lt;span class="k"&gt;**&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;.dbmdl
&lt;span class="k"&gt;**&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;.jfm
&lt;span class="k"&gt;**&lt;/span&gt;/charts
&lt;span class="k"&gt;**&lt;/span&gt;/docker-compose&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;**&lt;/span&gt;/compose.y&lt;span class="k"&gt;*&lt;/span&gt;ml
&lt;span class="k"&gt;**&lt;/span&gt;/Dockerfile&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;**&lt;/span&gt;/node_modules
&lt;span class="k"&gt;**&lt;/span&gt;/npm-debug.log
&lt;span class="k"&gt;**&lt;/span&gt;/obj
&lt;span class="k"&gt;**&lt;/span&gt;/secrets.dev.yaml
&lt;span class="k"&gt;**&lt;/span&gt;/values.dev.yaml
&lt;span class="k"&gt;**&lt;/span&gt;/build
&lt;span class="k"&gt;**&lt;/span&gt;/dist
LICENSE
README.md
&lt;span class="k"&gt;**&lt;/span&gt;/backend
/&lt;span class="k"&gt;*&lt;/span&gt;.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. &lt;strong&gt;Building Docker Image:&lt;/strong&gt; Building Docker Image and Running it&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/fnJCDv_LGYY" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Now we are all set for building our &lt;strong&gt;Docker image&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After saving all the files&lt;/p&gt;

&lt;p&gt;Go to your terminal and go to the frontend directory by running the command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;frontend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you are in the frontend directory. Now, let's build a Docker image&lt;/p&gt;

&lt;p&gt;Run the build command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; my-frontend-todo-app:v1 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down the above command&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker build&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is the core instruction. It tells the Docker engine to read a Dockerfile and "bake" it into a static image (a snapshot of your application).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-t my-frontend-todo-app:v1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-t (Tag):&lt;/strong&gt; This flag lets you name your image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;my-frontend-todo-app:&lt;/strong&gt; our image name&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;:v1&lt;/strong&gt; Version tag allows us to provide a version to our build. If we leave this, our Docker default uses the &lt;strong&gt;latest&lt;/strong&gt; as the version.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.&lt;/code&gt; —&amp;gt; (The Dot) Most important part! It effectively says: "Look for the Dockerfile and the source code in the current folder I am in right now."&lt;/p&gt;

&lt;p&gt;Once you run the above command and it build docker image successfully. Your terminal  looks like this:&lt;/p&gt;

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

&lt;p&gt;You can also check the Docker image by going to Docker Desktop inside the image tab.&lt;/p&gt;

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

&lt;p&gt;The diagram illustrates what we have done so far.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyt821yf3ousz3jsabsd5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyt821yf3ousz3jsabsd5.png" alt="High level view of docker flow from source code to docke image" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We successfully created our frontend image. Now it's time to run it.&lt;/p&gt;

&lt;p&gt;In the same terminal where you built the image, run the command below or open a new terminal and cd to frontend if you are not in the frontend directory.&lt;/p&gt;

&lt;p&gt;And run the command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="nt"&gt;--name&lt;/span&gt; my-todo-app-container my-frontend-todo-app:v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you run the above command, your terminal looks like this:&lt;/p&gt;

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

&lt;p&gt;Let's break down the above command&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker run&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The command to start a new container from an image.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-it&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This runs the container in Interactive Mode. Instead of running silently in the background, it attaches the container to your current terminal window so you can see the logs (Nginx output) immediately.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-p 8080:80&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;(Port Mapping) This acts as a bridge between your computer and the container.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8080&lt;/strong&gt; —&amp;gt; The port on your laptop. You will access the site at &lt;strong&gt;&lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;80&lt;/strong&gt; —&amp;gt; The port inside the container where Nginx is listening.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-name my-todo-app-container&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Gives the container a custom name. Without this, Docker assigns a random name. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;my-frontend-todo-app:v1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Tells Docker exactly which image and version tag to use as the blueprint for this container.&lt;/p&gt;

&lt;p&gt;Note:&lt;br&gt;
Right now, we are running the Docker container in interactive mode, which has hijacked our current terminal. If you don’t want that, run the Docker container in detach mode so that the Docker container runs in the background without hijacking our terminal. &lt;/p&gt;

&lt;p&gt;The command will run the container in detach mode. Just replace -it with &lt;strong&gt;-d&lt;/strong&gt;. You can play around with it more&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="nt"&gt;--name&lt;/span&gt; my-todo-app-container my-frontend-todo-app:v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the above command is successful, it starts showing in Docker Desktop. Go and check the container section in Docker Desktop&lt;/p&gt;

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

&lt;p&gt;Now go to the browser and go to the URL &lt;strong&gt;&lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;&lt;/strong&gt;. Now you are able to see our to-do app. &lt;/p&gt;

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

&lt;p&gt;Hurray, our app is perfectly built and up and running.&lt;/p&gt;

&lt;p&gt;See the image below for a high-level view. What’s going on&lt;/p&gt;

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

&lt;p&gt;Let's test out the whole flow&lt;/p&gt;

&lt;p&gt;If your backend is not running. Go to the &lt;strong&gt;backend directory&lt;/strong&gt; and spin up the backend server. By running&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Note: Make sure your MySQL container is up and running&lt;/p&gt;

&lt;p&gt;Once our backend is up and running. Go to the browser where our frontend app is running and open the network panel. Or go to the URL &lt;strong&gt;&lt;a href="http://localhost:8080/" rel="noopener noreferrer"&gt;http://localhost:8080/&lt;/a&gt;&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Open the network tab and in the filter select &lt;code&gt;fetch/xhr&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now refresh this page. We are able to see &lt;strong&gt;undefined&lt;/strong&gt; in the network panel. Click on it to open the details of the request.&lt;/p&gt;

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

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

&lt;p&gt;Ok, but why is undefined going in the request? &lt;/p&gt;

&lt;p&gt;Let’s try to add a new todo and see what’s going to happen &lt;/p&gt;

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

&lt;p&gt;It is still giving me &lt;strong&gt;undefined&lt;/strong&gt; in the URL. What’s going on&lt;/p&gt;

&lt;p&gt;Our backend server is running on port &lt;strong&gt;5500&lt;/strong&gt;, but the frontend call goes to &lt;code&gt;http://localhost:8080/undefined&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Why, because we messed up with the &lt;strong&gt;env&lt;/strong&gt; variable.&lt;/p&gt;

&lt;p&gt;Check your .env file in the frontend&lt;/p&gt;

&lt;p&gt;It looks like below&lt;/p&gt;

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

&lt;p&gt;This is the correct URL for our backend. But we do not pass our env variable to the frontend, that’s why it’s calling the wrong URL with undefined.&lt;/p&gt;

&lt;p&gt;So, how can we pass our environment variable in a Docker image? In the next section, we will talk about it.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. &lt;strong&gt;Managing Environment Variables:&lt;/strong&gt; Configuring API URLs and secrets securely.&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/qwfhFLNjMsk" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Handling environment variables is crucial and requires extra precaution. So that we do not expose our secrets accidentally in public.&lt;/p&gt;

&lt;p&gt;Let's understand how to handle env variables&lt;/p&gt;

&lt;p&gt;There are two ways we can pass the env variable in our Docker image&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Passing env variables during build time using build args.&lt;/li&gt;
&lt;li&gt;Passing env variables at run-time by injecting them into the &lt;strong&gt;window&lt;/strong&gt; object.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's discuss both of them one by one&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Passing env variable during build time using build args:
&lt;/h4&gt;

&lt;p&gt;In this approach, when we are building the image, we only need to pass the env variable using the &lt;strong&gt;build arguments&lt;/strong&gt; into our image.&lt;br&gt;
These variables become static. In order to change the env variable, we need to rebuild the image.&lt;/p&gt;

&lt;p&gt;This approach we mostly avoid because it is not efficient. Because we need to rebuild the image every time if the environment variable changes. And building images is a costly or time-consuming process.&lt;/p&gt;

&lt;p&gt;Ok, enough talk now, let’s try out&lt;/p&gt;

&lt;p&gt;First things first, we need to add the build arg &lt;code&gt;VITE_API_URL&lt;/code&gt; in our Docker file. Add it after the line&lt;br&gt;
&lt;code&gt;COPY . .&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Build arguments for environment variables&lt;/span&gt;
&lt;span class="s"&gt;ARG VITE_API_URL&lt;/span&gt;
&lt;span class="s"&gt;ENV VITE_API_URL=$VITE_API_URL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now our Dockerfile looks like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="s"&gt;FROM node:lts&lt;/span&gt;

&lt;span class="c1"&gt;# Install nginx on Debian&lt;/span&gt;
&lt;span class="s"&gt;RUN apt-get update &amp;amp;&amp;amp; \&lt;/span&gt;
    &lt;span class="s"&gt;apt-get install -y nginx&lt;/span&gt;

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

&lt;span class="s"&gt;COPY package*.json ./&lt;/span&gt;
&lt;span class="s"&gt;RUN npm ci&lt;/span&gt;

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

&lt;span class="c1"&gt;# Build arguments for environment variables&lt;/span&gt;
&lt;span class="s"&gt;ARG VITE_API_URL&lt;/span&gt;
&lt;span class="s"&gt;ENV VITE_API_URL=$VITE_API_URL&lt;/span&gt;

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

&lt;span class="c1"&gt;# Ensure nginx directories exist&lt;/span&gt;
&lt;span class="s"&gt;RUN mkdir -p /frontend-app/mywebsite&lt;/span&gt;

&lt;span class="c1"&gt;# Copy nginx config&lt;/span&gt;
&lt;span class="s"&gt;COPY nginx.conf /etc/nginx/sites-available/default&lt;/span&gt;

&lt;span class="c1"&gt;# Move dist files&lt;/span&gt;
&lt;span class="s"&gt;RUN cp -r ./dist/* /frontend-app/mywebsite/&lt;/span&gt;

&lt;span class="s"&gt;EXPOSE &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;
&lt;span class="s"&gt;CMD ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now build a new image by passing the build args as our env variables. In our case, it's a backend URL. Go to the terminal where we ran our Docker container. Stop it by pressing Ctrl + c. It stops the running container.&lt;/p&gt;

&lt;p&gt;Then run the &lt;code&gt;docker build&lt;/code&gt; command by passing the &lt;code&gt;--build-arg&lt;/code&gt; flag with the build arg &lt;code&gt;VITE_API_URL&lt;/code&gt;, with our backend URL.&lt;/p&gt;

&lt;p&gt;Like &lt;code&gt;--build-arg VITE_API_URL=http://localhost:5500/api/todos&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Copy Api backend URL from the frontend &lt;code&gt;.env&lt;/code&gt; file. And then pass that in &lt;br&gt;
docker build command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;--build-arg&lt;/span&gt; &lt;span class="nv"&gt;VITE_API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:5500/api/todos &lt;span class="nt"&gt;-t&lt;/span&gt; my-frontend-todo-app-with-env:v1 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it's built successfully, it looks something like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu0tq3ju06vrncl4t70y0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu0tq3ju06vrncl4t70y0.png" alt="Output from terminal after success full build args command" width="798" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now run it via the docker run command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="nt"&gt;--name&lt;/span&gt; my-todo-container-with-env my-frontend-todo-app-with-env:v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the command above, we ran it in detach mode, so it will not hijack our terminal. It will start the container in the background.&lt;/p&gt;

&lt;p&gt;If the above command is successful, you will see something like this in the terminal&lt;/p&gt;

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

&lt;p&gt;You can also verify from the Docker Desktop&lt;/p&gt;

&lt;p&gt;Now go to &lt;code&gt;http://localhost:8080&lt;/code&gt; or refresh the page if you already have it open in the browser&lt;/p&gt;

&lt;p&gt;Woo, we can see two seeded todos from the database, and we are now hitting the correct URL.&lt;/p&gt;

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

&lt;p&gt;We're done with this approach. Let’s see the second approach now&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Passing env variables at run-time by injecting them into the &lt;strong&gt;window&lt;/strong&gt; object
&lt;/h4&gt;

&lt;p&gt;So the idea is that somehow we will provide an env variable to our frontend at runtime so that we don’t need to build images every time.&lt;/p&gt;

&lt;p&gt;So How we do this?&lt;/p&gt;

&lt;p&gt;Our frontend is running on a browser. They cannot access the Docker container's environment variables (like process.env) at runtime.&lt;br&gt;
So when the browser requests index.html from the server. We inject one JS script into our index.html. That script will inject our env variable into the &lt;strong&gt;window&lt;/strong&gt; object.&lt;/p&gt;

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

&lt;p&gt;But this approach only works if your environment does not contain any sensitive data. In general, we avoid putting sensitive data like api key, password, etc. in the frontend. Because with this approach, anyone can see your env variable in the console by accessing the window object.&lt;/p&gt;

&lt;p&gt;Let’s implement  this as well&lt;/p&gt;

&lt;p&gt;Comment out those lines that we added previously for build args.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Build arguments for environment variables&lt;/span&gt;
&lt;span class="c1"&gt;# ARG VITE_API_URL&lt;/span&gt;
&lt;span class="c1"&gt;# ENV VITE_API_URL=$VITE_API_URL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Now, inside the frontend directory, create a file &lt;code&gt;nginx-entrypoint.sh&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And copy the snapshot below and paste it into &lt;code&gt;nginx-entrypoint.sh&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c"&gt;# Start the generated config file&lt;/span&gt;
&lt;span class="nv"&gt;CONFIG_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/frontend-app/mywebsite/env-config.js"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"window.env = {"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$CONFIG_PATH&lt;/span&gt;

&lt;span class="c"&gt;# Find all environment variables starting with "VITE_" and add them to the file&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;var &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;env&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"VITE_"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$var&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"="&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; 1&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$var&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"="&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; 2-&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  &lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$CONFIG_PATH&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# End the config file&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"};"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$CONFIG_PATH&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Environment variable injection completed."&lt;/span&gt;
&lt;span class="nb"&gt;exec &lt;/span&gt;nginx &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="s1"&gt;'daemon off;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;nginx-entrypoint.sh&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This script runs immediately before Nginx starts. It takes the environment variables from Docker and writes them into a real JavaScript file that the browser can read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Line-by-Line Breakdown&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the File (echo "window.env..."). It creates a new JavaScript file called &lt;code&gt;env-config.js&lt;/code&gt; inside the &lt;strong&gt;/frontend-app/mywebsite&lt;/strong&gt; and starts defining a global object: window.env = {. &lt;/li&gt;
&lt;li&gt;The Loop (for var in...) It looks through all the system environment variables currently set in the container.

&lt;ul&gt;
&lt;li&gt;grep &lt;code&gt;"VITE_"&lt;/code&gt;: It filters the list to only grab variables that start with VITE_ (security measure to avoid exposing secrets like internal keys). &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The Injection (echo " $name: \"$value\","). It extracts the Name and Value of the variable and writes them into the JS file as a key-value pair.

&lt;ul&gt;
&lt;li&gt;Result inside file: VITE_API_URL: &lt;code&gt;https://my-api.com&lt;/code&gt;, &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Close &amp;amp; Launch (exec nginx...) It closes the JS object (};) and finally starts the Nginx server to serve your website.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Final Result: Our frontend code can now access variables via window.env.VITE_API_URL.&lt;/p&gt;

&lt;p&gt;Once the above file is saved. Now we want to run this script before nginx starts so that we can inject the env variable at runtime. To do that&lt;/p&gt;

&lt;p&gt;Go to Dockerfile  and add the lines after the &lt;code&gt;RUN mkdir -p /frontend-app/mywebsite&lt;/code&gt; command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;COPY nginx-entrypoint.sh /frontend-app/nginx-entrypoint.sh&lt;/span&gt;
&lt;span class="s"&gt;RUN sed -i 's/\r$//' /frontend-app/nginx-entrypoint.sh&lt;/span&gt;
&lt;span class="s"&gt;RUN chmod +x /frontend-app/nginx-entrypoint.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are copying &lt;strong&gt;nginx-entrypoint.sh&lt;/strong&gt; into Docker, converting any Windows-style line endings to Linux format to prevent execution errors, and giving it executable permission so that Docker can run this script.&lt;/p&gt;

&lt;p&gt;After adding the above lines, our Docker file looks like this:&lt;/p&gt;

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

&lt;p&gt;Now, comment out this line&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# CMD ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And after the above line, add&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;ENTRYPOINT ["/frontend-app/nginx-entrypoint.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command tells Docker: "No matter what else happens, run this script first when the container starts."&lt;/p&gt;

&lt;p&gt;It guarantees your environment variables are injected every single time the container launches.&lt;/p&gt;

&lt;p&gt;Finally, our Dockerfile will look like this&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c"&gt;# Updates the Linux package manager and installs Nginx&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nginx

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

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

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

&lt;span class="c"&gt;# Build arguments for environment variables&lt;/span&gt;
&lt;span class="c"&gt;# ARG VITE_API_URL&lt;/span&gt;
&lt;span class="c"&gt;# ENV VITE_API_URL=$VITE_API_URL&lt;/span&gt;

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

&lt;span class="c"&gt;# Ensure nginx directories exist&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /frontend-app/mywebsite
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; nginx-entrypoint.sh /frontend-app/nginx-entrypoint.sh&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/\r$//'&lt;/span&gt; /frontend-app/nginx-entrypoint.sh
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /frontend-app/nginx-entrypoint.sh

&lt;span class="c"&gt;# Copy nginx config&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; nginx.conf /etc/nginx/sites-available/default&lt;/span&gt;


&lt;span class="c"&gt;# Move dist files&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ./dist/&lt;span class="k"&gt;*&lt;/span&gt; /frontend-app/mywebsite/

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80&lt;/span&gt;
&lt;span class="c"&gt;# CMD ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/frontend-app/nginx-entrypoint.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are almost done with this approach; only a small change is required.&lt;/p&gt;

&lt;p&gt;As we know, this script will generate an &lt;code&gt;env-config.js&lt;/code&gt; file. We also need to add this script to our &lt;code&gt;index.html&lt;/code&gt;, then only the browser downloads it, and then only the JavaScript script executes and adds our env variables into the &lt;strong&gt;window&lt;/strong&gt; object.&lt;/p&gt;

&lt;p&gt;Go to &lt;code&gt;index.html&lt;/code&gt; of the frontend directory and add the line below inside the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/env-config.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;index.html&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flfqzmfgeb239cv16d8su.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flfqzmfgeb239cv16d8su.png" alt="index html file after adding env script in head" width="799" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we're done with this setup, let’s build a new image. Make sure you are in the frontend directory.&lt;/p&gt;

&lt;p&gt;Run the build command in the terminal for building the image&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; my-frontend-todo-app-with-runtime-env:v1 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s build our image if everything is fine.&lt;/p&gt;

&lt;p&gt;Now, how to pass the env variable so that our &lt;code&gt;nginx-entrypoint.sh&lt;/code&gt; reads our env variable.&lt;/p&gt;

&lt;p&gt;We can pass an env variable or a whole env file via the Docker run command. To run and pass environment variables into a Docker container, run the command below.&lt;/p&gt;

&lt;p&gt;Note:&lt;/p&gt;

&lt;p&gt;Before running the command. Make sure port &lt;strong&gt;8080&lt;/strong&gt; is free. If your frontend container is running on port &lt;strong&gt;8080&lt;/strong&gt;. Please first stop it.&lt;br&gt;
Via Docker Desktop.&lt;/p&gt;

&lt;p&gt;Go to Docker Desktop and click on the action button&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="nt"&gt;--env-file&lt;/span&gt; ./.env &lt;span class="nt"&gt;--name&lt;/span&gt; my-todo-container-with-runtime-env my-frontend-todo-app-with-runtime-env:v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the &lt;code&gt;--env-file&lt;/code&gt; flag is used to pass the whole &lt;strong&gt;.env&lt;/strong&gt; file.&lt;/p&gt;

&lt;p&gt;Once your above command is successful, our container starts successfully&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F89oqdyxbsa187sua4sei.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F89oqdyxbsa187sua4sei.png" alt="terminal o/p after running docker run command with runtime env" width="800" height="43"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now our container is running successfully. Go to the browser and check &lt;code&gt;http://localhost:8080&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Hurray👏, we can see our todos&lt;/p&gt;

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

&lt;p&gt;We've done it. You can check it in the window object by going to the browser devtools &lt;strong&gt;console&lt;/strong&gt; panel and typing&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see &lt;code&gt;VITE_API_URL&lt;/code&gt; &lt;strong&gt;env variables&lt;/strong&gt; in the devtool console panel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyj8b5mdrdtbrl0vwzq79.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyj8b5mdrdtbrl0vwzq79.png" alt="Console output for env variable in the window object" width="799" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We successfully passed our ENV_variable at runtime. I mostly prefer this approach when my environment variable contains no sensitive information.&lt;/p&gt;

&lt;p&gt;So we're done with containerizing our frontend app. Now, let’s look at how to optimize the size of our Docker image. And make it production-ready&lt;/p&gt;

&lt;p&gt;In the next section, we will discuss it.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. &lt;strong&gt;Docker File optimization:&lt;/strong&gt; Intro to Docker Image Optimization.&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/h6WIL_ILZ9Y" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Let’s first check the size of our images&lt;/p&gt;

&lt;p&gt;Run the below command to see the list of images. Remember, we need to focus on the image &lt;strong&gt;my-frontend-todo-app-with-runtime-env:v1&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



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

&lt;p&gt;See the size of our image, it is &lt;strong&gt;1.28 GB&lt;/strong&gt;. And see also the MySQL DB image size &lt;strong&gt;777mb&lt;/strong&gt;. Our image size is greater than MySQL. &lt;/p&gt;

&lt;p&gt;Note: These image sizes may vary from system to system. So don't worry about it. Just understand the intent.&lt;/p&gt;

&lt;p&gt;This is not good; we need to reduce the size of the image because in production, we can’t use very large images. Also, when Docker downloads this image from the registry, it takes a lot of time.&lt;/p&gt;

&lt;p&gt;So, how can we reduce image size?&lt;/p&gt;

&lt;p&gt;Right now, we are using the &lt;strong&gt;node:lts&lt;/strong&gt; image for Node.js. Which is a standard image and not lightweight.&lt;/p&gt;

&lt;p&gt;In Docker for production usage, we mostly prefer images like Alpine, slim and distroless images. These images are lightweight and recommended for use.&lt;/p&gt;

&lt;p&gt;Note:&lt;br&gt;
If you are building any banking application where security is a concern, then go with distroless images. These images do not contain any bash, package manager, or Hardest to hack because a hacker is unable to execute any command on it. But also the hardest to debug.&lt;/p&gt;

&lt;p&gt;For our use case, we will use an Alpine image of Node.js. &lt;/p&gt;

&lt;p&gt;We need to make some changes to our Dockerfile. So that we can use an Alpine image.&lt;/p&gt;

&lt;p&gt;Change&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;FROM node:lts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;FROM node:lts-alpine3.22&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;RUN apt-get update &amp;amp;&amp;amp; \&lt;/span&gt;
     &lt;span class="s"&gt;apt-get install -y nginx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;RUN apk add --no-cache nginx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;COPY nginx.conf /etc/nginx/sites-available/default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;COPY nginx.conf /etc/nginx/http.d/default.conf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After changes, our Dockerfile looks like below&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; nginx    

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

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

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

&lt;span class="c"&gt;# Build arguments for environment variables&lt;/span&gt;
&lt;span class="c"&gt;# ARG VITE_API_URL&lt;/span&gt;
&lt;span class="c"&gt;# ENV VITE_API_URL=$VITE_API_URL&lt;/span&gt;

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

&lt;span class="c"&gt;# Ensure nginx directories exist&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /frontend-app/mywebsite
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; nginx-entrypoint.sh /frontend-app/nginx-entrypoint.sh&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/\r$//'&lt;/span&gt; /frontend-app/nginx-entrypoint.sh
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /frontend-app/nginx-entrypoint.sh

&lt;span class="c"&gt;# Copy nginx config&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; nginx.conf /etc/nginx/http.d/default.conf&lt;/span&gt;


&lt;span class="c"&gt;# Move dist files&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ./dist/&lt;span class="k"&gt;*&lt;/span&gt; /frontend-app/mywebsite/

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80&lt;/span&gt;
&lt;span class="c"&gt;# CMD ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/frontend-app/nginx-entrypoint.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let’s build this image and run it. But before doing that, we need to stop the frontend container that we started previously.&lt;/p&gt;

&lt;p&gt;Go to Docker Desktop and click on the action button &lt;/p&gt;

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

&lt;p&gt;Run the build command for building the image. Run inside the frontend directory&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; my-frontend-todo-optimize-app:v1 &lt;span class="nb"&gt;.&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run a container, run the command below in the terminal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="nt"&gt;--env-file&lt;/span&gt; ./.env &lt;span class="nt"&gt;--name&lt;/span&gt; my-frontend-todo-optimize-app my-frontend-todo-optimize-app:v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, once it runs successfully, go to the browser and check &lt;code&gt;http://localhost:8080&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;It’s working fine, and it's able to access our backend as well.&lt;/p&gt;

&lt;p&gt;Note:&lt;br&gt;
If you face any issue on the backend, like the todos api getting 500, then stop and then again restart the backend server.&lt;/p&gt;

&lt;p&gt;Now go to the terminal and run the &lt;strong&gt;docker images&lt;/strong&gt; command, and now check the size of the container.&lt;/p&gt;

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

&lt;p&gt;See that from 1 GB to 281 MB we drastically reduce the size. &lt;/p&gt;

&lt;p&gt;Can we optimize our image further? &lt;/p&gt;

&lt;p&gt;Is this image is production ready?&lt;/p&gt;

&lt;p&gt;Do we build images like this in production? Or is there any other way?&lt;/p&gt;

&lt;p&gt;We will talk about it in the next section.&lt;/p&gt;
&lt;h3&gt;
  
  
  10. &lt;strong&gt;Multi-Stage Docker file:&lt;/strong&gt; The professional way to shrink image size.&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/nh9vakyqc3Y" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;For production, a multi-stage Docker file is recommended. &lt;/p&gt;

&lt;p&gt;So the Dockerfile we built so far is a single-stage Dockerfile. There is only one stage.&lt;/p&gt;

&lt;p&gt;The drawback of a single-stage Docker file is that it attempts to do everything—downloading source code, installing dependencies, building the app, and running it—inside the same environment.&lt;/p&gt;

&lt;p&gt;This increases the size of the images. Also, it exposes the attack surface for hackers because it contains more tools that will give hackers more chances to hack it.&lt;/p&gt;

&lt;p&gt;But if we use a multi-stage Docker build, we get rid of these problems&lt;/p&gt;

&lt;p&gt;So In multi stage docker file. We build our image in multiple stages. Like the build stage, final stage or prod stage.&lt;/p&gt;

&lt;h4&gt;
  
  
  In the build stage
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;We copy our source code and package.json, package-lock.json files.&lt;/li&gt;
&lt;li&gt;Then we install all the dependencies.&lt;/li&gt;
&lt;li&gt;And finally, we build the code.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  In Final stage or Prod Stage
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;We install the &lt;strong&gt;nginx&lt;/strong&gt; image&lt;/li&gt;
&lt;li&gt;Copy the &lt;strong&gt;dist&lt;/strong&gt; folder from the &lt;strong&gt;build stage&lt;/strong&gt; and put it inside the web server directory.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because in Prod we only need a &lt;strong&gt;web server&lt;/strong&gt; and our &lt;strong&gt;build folder&lt;/strong&gt;, that’s it.&lt;/p&gt;

&lt;p&gt;And in a multi-stage Dockerfile, the final image only had these two things.&lt;/p&gt;

&lt;p&gt;Enough talk, let’s write our multi-stage Docker file&lt;/p&gt;

&lt;p&gt;First things first, comment out the older content of the Docker file. Once you comment out the Docker file, you will be able to see some errors in VS Code. But don't worry about it. &lt;/p&gt;

&lt;p&gt;Then copy the content below after the commented content&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;FROM node:lts-alpine3.22 AS build&lt;/span&gt;
&lt;span class="s"&gt;WORKDIR /frontend-app&lt;/span&gt;
&lt;span class="s"&gt;RUN --mount=type=bind,source=package.json,target=package.json \&lt;/span&gt;
    &lt;span class="s"&gt;--mount=type=bind,source=package-lock.json,target=package-lock.json \&lt;/span&gt;
    &lt;span class="s"&gt;--mount=type=cache,target=/root/.npm \&lt;/span&gt;
    &lt;span class="s"&gt;npm ci&lt;/span&gt;

&lt;span class="c1"&gt;# Copy the rest of the source files into the image.&lt;/span&gt;

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

&lt;span class="c1"&gt;# Run the build script.&lt;/span&gt;

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

&lt;span class="s"&gt;FROM nginx:stable-alpine-slim AS final&lt;/span&gt;

&lt;span class="s"&gt;COPY nginx-entrypoint.sh /frontend-app/nginx-entrypoint.sh&lt;/span&gt;
&lt;span class="s"&gt;RUN sed -i 's/\r$//' /frontend-app/nginx-entrypoint.sh&lt;/span&gt;
&lt;span class="s"&gt;RUN chmod +x /frontend-app/nginx-entrypoint.sh&lt;/span&gt;

&lt;span class="c1"&gt;# Copy the production dependencies from the build stage&lt;/span&gt;
&lt;span class="s"&gt;COPY --from=build /frontend-app/nginx.conf /etc/nginx/conf.d/default.conf&lt;/span&gt;
&lt;span class="s"&gt;COPY --from=build /frontend-app/dist /frontend-app/mywebsite&lt;/span&gt;


&lt;span class="c1"&gt;# Expose the port that the application listens on.&lt;/span&gt;
&lt;span class="s"&gt;EXPOSE &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;
&lt;span class="s"&gt;ENTRYPOINT ["/frontend-app/nginx-entrypoint.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Line-by-line explanation&lt;/p&gt;

&lt;p&gt;So here we define two stages, one is &lt;strong&gt;build&lt;/strong&gt; and another is &lt;strong&gt;final&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Build Stage:
&lt;/h4&gt;

&lt;p&gt;We set up &lt;strong&gt;node js&lt;/strong&gt; environment for building our application.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;FROM node:20-alpine3.22 AS build&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Think of &lt;code&gt;AS build&lt;/code&gt; like giving a name tag to a specific step in your setup. It simply lets you tell Docker later on, 'Hey, remember that step we named 'build'? Go grab the files from there.&lt;/p&gt;

&lt;p&gt;The above line tells Docker to download a lightweight version of Linux ("Alpine") that has Node.js LTS version installed.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;WORKDIR /frontend-app&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Creates a folder inside the container at &lt;strong&gt;/frontend-app&lt;/strong&gt; and sets it as the default location for all future commands.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RUN --mount=type=bind,source=package.json,target=package.json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This command looks different. Usually, we do &lt;code&gt;COPY package.json .&lt;/code&gt; This physically &lt;strong&gt;duplicates&lt;/strong&gt; the file into a layer of our Docker image.&lt;/p&gt;

&lt;p&gt;But &lt;code&gt;--mount=type=bind,source=package.json,target=package.json&lt;/code&gt;. In this way, we are telling to Docker Don't copy the file. Just let me &lt;strong&gt;'borrow' package.json&lt;/strong&gt; from my computer (source) and make it visible inside the container at package.json (target) just for the duration of this command.&lt;/p&gt;

&lt;p&gt;It keeps the image layer cleaner. As soon as the command finishes, the file is technically no longer "in" that layer, because it was never copied, only mounted.&lt;/p&gt;

&lt;p&gt;Note: The second line does the same thing for the &lt;strong&gt;package-lock.json&lt;/strong&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--mount=type=cache,target=/root/.npm&lt;/code&gt; &lt;/p&gt;

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

&lt;p&gt;Normally, Docker containers start fresh. When you run npm install, it downloads megabytes of files from the internet. If you rebuild the image, it forgets those downloads and does it all over again.&lt;/p&gt;

&lt;p&gt;This command tells Docker to create a special storage bucket (cache) on your host machine (your computer) and mount it to &lt;strong&gt;/root/.npm&lt;/strong&gt; inside the container (which is where npm stores its cache).&lt;/p&gt;

&lt;p&gt;So now npm downloads libraries and saves them to &lt;strong&gt;/root/.npm&lt;/strong&gt;. As cache, and when we try to rebuild the image. It just reaches out to our host machine( our laptop) storage and grabs the files instead of downloading them from the internet again.&lt;/p&gt;

&lt;p&gt;This speeds up the process of installing dependencies. Also not store &lt;strong&gt;package.json&lt;/strong&gt; or &lt;strong&gt;package-lock.json&lt;/strong&gt; files in our image because we don’t need these files in our final image as well.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm ci&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Installs your project dependencies.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;COPY . .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Copies all the files from your laptop (current directory) into the container's current folder (/frontend-app)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RUN npm run build&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This usually creates a dist folder containing the final website files.&lt;/p&gt;

&lt;h4&gt;
  
  
  Final Stage:
&lt;/h4&gt;

&lt;p&gt;This section throws away the heavy Node.js tools and starts fresh with a tiny web server.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;FROM nginx:stable-alpine-slim AS final&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We use a "slim" version of Nginx. It doesn't have Node.js; it only knows how to serve web files.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;COPY nginx-entrypoint.sh /frontend-app/nginx-entrypoint.sh&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Copies a custom script file from your laptop to the root of the container.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RUN sed -i 's/\r$//' /frontend-app/nginx-entrypoint.sh&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Removes hidden Windows line endings from the script so it doesn't crash inside the Linux container.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RUN chmod +x /frontend-app/nginx-entrypoint.sh&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Changes the permissions of that script to make it executable (so it can be run as a program).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;COPY --from=build /frontend-app/nginx.conf /etc/nginx/conf.d/default.conf&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It reaches back into the build stage to grab our custom configuration file and places it into the final Nginx folder to overwrite the server's default settings. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;COPY --from=build /frontend-app/dist /frontend-app/mywebsite&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Copies the compiled website from the &lt;strong&gt;dist&lt;/strong&gt; folder from the "build" stage to a folder named /frontend-app/mywebsite in the final stage.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EXPOSE 80&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This container listens on port 80&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ENTRYPOINT ["/frontend-app/nginx-entrypoint.sh"]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Specifies the script that must run every time the container starts up.&lt;/p&gt;

&lt;p&gt;If you didn't use multi-stage builds, your final image would include all of Node.js, your source code, and thousands of dev dependencies, making it heavier. By using this method, your final image only contains Nginx and your static files, this make your final build lighter.&lt;/p&gt;

&lt;p&gt;Now let’s build an image and then run it to check if it’s working fine or not.&lt;/p&gt;

&lt;p&gt;In the terminal, go to your frontend directory and run the build command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; my-frontend-todo-app-multistage:v1 &lt;span class="nb"&gt;.&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's build successfully&lt;/p&gt;

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

&lt;p&gt;We build it successfully. Now let’s run it. &lt;/p&gt;

&lt;p&gt;Note: Before running the container. Please stop the already running container on port 8080.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--env-file&lt;/span&gt; ./.env &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="nt"&gt;--name&lt;/span&gt; todo-app-multistage-container my-frontend-todo-app-multistage:v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our container is running now&lt;/p&gt;

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

&lt;p&gt;Now go to the browser and open the URL &lt;code&gt;http://localhost:8080&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;Hurray, it’s working fine. Make sure your backend server is running.&lt;/p&gt;

&lt;p&gt;Now, let’s check the size of our image. Go to the terminal and run&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



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

&lt;p&gt;See the image size now, from 281 MB to 13.4 MB. Now our image is ready for prod usage.&lt;/p&gt;

&lt;p&gt;We're done with our image optimization. Is it ready for production environments? Not yet, we need to secure it as well. We will discuss it in the upcoming section.&lt;/p&gt;

&lt;h3&gt;
  
  
  11. &lt;strong&gt;Securing Frontend Docker Container:&lt;/strong&gt; Restricting folder permissions and user access.&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Right now, our container is running as a &lt;strong&gt;root&lt;/strong&gt; user. This is not recommended in a production environment. If an attacker manages to get the access of our server. Then they can take over the entire server. Which is soo scary. &lt;/p&gt;

&lt;p&gt;To completely secure our container, we need to run it using the &lt;strong&gt;least privileged user&lt;/strong&gt;. This ensures that even if an attacker gains access to the container, they cannot execute malicious code or alter server permissions. They are effectively locked down and stripped of the authority to compromise the system.&lt;/p&gt;

&lt;p&gt;So, before we proceed, we need to understand how Linux file permissions work.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. The Three Security Scopes (The "Who")
&lt;/h4&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/RmMkmOAaCmI" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Every file and directory in Linux evaluates access requests sequentially against three distinct user scopes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Owner (&lt;code&gt;u&lt;/code&gt;):&lt;/strong&gt; The system administrator who created the resource (usually &lt;code&gt;root&lt;/code&gt; in a container).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group (&lt;code&gt;g&lt;/code&gt;):&lt;/strong&gt; The specific service account or application tier assigned to the resource (e.g., sudo group).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Others (&lt;code&gt;o&lt;/code&gt;):&lt;/strong&gt; Unauthenticated entities or unrelated system users. All the other users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you issue a command like &lt;code&gt;chmod 750&lt;/code&gt;, you are passing a three-digit configuration payload. The &lt;strong&gt;first digit&lt;/strong&gt; applies to the Owner, the &lt;strong&gt;second&lt;/strong&gt; to the Group, and the &lt;strong&gt;third&lt;/strong&gt; to Others.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. The Binary Breakdown: Read, Write, and Execute
&lt;/h4&gt;

&lt;p&gt;Linux permissions do not use complex configuration files. They are based entirely on three simple actions, mapped to a 3-bit binary system where &lt;code&gt;1&lt;/code&gt; is ON (allowed) and &lt;code&gt;0&lt;/code&gt; is OFF (denied). &lt;/p&gt;

&lt;p&gt;Here are the only three actions a user can take:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read:&lt;/strong&gt; You can view the text inside a file, or see the list of files inside a folder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write:&lt;/strong&gt; You can edit, modify, or delete a file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute:&lt;/strong&gt; You can run a file as a script or program. &lt;em&gt;(Crucial gotcha: For **folders&lt;/em&gt;&lt;em&gt;, Execute simply means the ability to open or "enter" the folder).&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these three actions is assigned to a specific slot in a 3-bit binary number. When you convert that binary sequence into standard decimal numbers, you get the magic numbers used in &lt;code&gt;chmod&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read&lt;/strong&gt; is the first slot (&lt;code&gt;1 0 0&lt;/code&gt; in binary), which equals &lt;strong&gt;4&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write&lt;/strong&gt; is the middle slot (&lt;code&gt;0 1 0&lt;/code&gt; in binary), which equals &lt;strong&gt;2&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute&lt;/strong&gt; is the last slot (&lt;code&gt;0 0 1&lt;/code&gt; in binary), which equals &lt;strong&gt;1&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To give a user a combination of permissions, you simply turn on the binary bits you want, which adds the numbers together. &lt;/p&gt;

&lt;p&gt;For example: If you want a user to be able to Read and Execute, but &lt;em&gt;not&lt;/em&gt; Write, the binary is &lt;code&gt;1 0 1&lt;/code&gt;. If you add the standard numbers together (4 + 0 + 1), you get &lt;strong&gt;5&lt;/strong&gt;. If you want them to have all three powers (&lt;code&gt;1 1 1&lt;/code&gt;), it is 4 + 2 + 1 = &lt;strong&gt;7&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Practical Implementation (&lt;code&gt;chmod 750&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/-mQl5QGaTEw" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;We achieve this with the matrix &lt;strong&gt;&lt;code&gt;750&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Owner gets &lt;code&gt;7&lt;/code&gt; (4 + 2 + 1):&lt;/strong&gt; The binary flag is &lt;code&gt;111&lt;/code&gt;. Root has Read, Write, and Execute authority.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group gets &lt;code&gt;5&lt;/code&gt; (4 + 0 + 1):&lt;/strong&gt; The binary flag is &lt;code&gt;101&lt;/code&gt;. Group users get Read and Execute authority, but Write (&lt;code&gt;0&lt;/code&gt;) is strictly denied.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Others get &lt;code&gt;0&lt;/code&gt;:&lt;/strong&gt; The binary flag is &lt;code&gt;000&lt;/code&gt;. A total block for other users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you get some understanding of Linux permissions from above. Let's see it in action&lt;/p&gt;

&lt;p&gt;Right now, our Dockerfile runs as the &lt;strong&gt;root&lt;/strong&gt; user.&lt;/p&gt;

&lt;p&gt;To check it. Go inside the Docker container by using the docker exec command&lt;/p&gt;

&lt;p&gt;For Mac&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; todo-app-multistage-container sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Windows, run the command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;winpty docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; todo-app-multistage-container sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you're inside the container, run the &lt;code&gt;whoami&lt;/code&gt; command&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;You will see the &lt;strong&gt;root&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Like this&lt;/p&gt;

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

&lt;p&gt;Now go inside the &lt;code&gt;frontend-app&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /frontend-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the list of files in &lt;code&gt;/frontend-app&lt;/code&gt; by running the &lt;code&gt;ls&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;You will see a list of files as shown in the image below&lt;/p&gt;

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

&lt;p&gt;Now, let's try to simulate the same behaviour. If our server gets hacked by someone. What can an attacker do if he can get the &lt;strong&gt;root&lt;/strong&gt; access?&lt;/p&gt;

&lt;p&gt;He might delete the &lt;strong&gt;mywebsite&lt;/strong&gt; folder. Then our website crashed immediately. Because it contains all the static files.&lt;/p&gt;

&lt;p&gt;Let's see what happened if we delete the &lt;strong&gt;mywebsite&lt;/strong&gt; folder.&lt;/p&gt;

&lt;p&gt;Inside the &lt;strong&gt;frontend-app&lt;/strong&gt;, run the following command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; mywebsite/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command deletes the whole &lt;code&gt;mywebsite&lt;/code&gt; folder. &lt;/p&gt;

&lt;p&gt;Now, if you run the &lt;code&gt;ls&lt;/code&gt; command, you will not be able to see the &lt;code&gt;mywebsite&lt;/code&gt; folder.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



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

&lt;p&gt;Now go to our frontend URL &lt;code&gt;http://localhost:8080&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You will see a &lt;strong&gt;500&lt;/strong&gt; internal server error.&lt;/p&gt;

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

&lt;p&gt;See our website broke now. So, for mitigating this issue. We &lt;br&gt;
need to run our container with the least privileged user. So no one can delete or interrupt our resources on the server.&lt;/p&gt;

&lt;p&gt;How can we do it 🤔? &lt;/p&gt;

&lt;p&gt;So, to lock down our &lt;code&gt;Dockerfile&lt;/code&gt;. Need to do the following tasks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creating a group, e.g. &lt;code&gt;frontend_group&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Creating a user, e.g. &lt;code&gt;frontend_user&lt;/code&gt;, and adding it to the &lt;code&gt;frontend_group&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Creating the necessary folders required for running nginx properly.&lt;/li&gt;
&lt;li&gt;Then we need to set the owner for the folders. Which required protection.&lt;/li&gt;
&lt;li&gt;Then we need to lock down our files and folders. And give the least privilege to the groups and users.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, let's do each step one by one&lt;/p&gt;
&lt;h4&gt;
  
  
  1. Creating a group, e.g. &lt;code&gt;frontend_group&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;In your Docker file, after the last &lt;code&gt;COPY&lt;/code&gt; command. Add the following command for creating the group&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;RUN addgroup -S frontend_group&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breakdown of the above command&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RUN addgroup -S frontend_group&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;addgroup: This tells Linux to create a brand new group.&lt;/p&gt;

&lt;p&gt;-S: This stands for "System." It tells Linux to make this a system group rather than a normal user group. System groups don't have passwords and are specifically designed for running background services (like Nginx or Node), which makes them much more secure.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Creating a user, e.g. &lt;code&gt;frontend_user&lt;/code&gt;, and adding it to the &lt;code&gt;frontend_group&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;After that, to create a user and add it to the group, add the following command after the above command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;RUN adduser -S frontend_user -G frontend_group&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Pro-Tip: The One-Liner Optimization (&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;While writing those commands on two separate lines makes them easy to read, you will almost always see them combined in professional Dockerfiles like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-S&lt;/span&gt; frontend_group &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="nt"&gt;-S&lt;/span&gt; frontend_user &lt;span class="nt"&gt;-G&lt;/span&gt; frontend_group
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why do we do this? Docker Layers.
&lt;/h3&gt;

&lt;p&gt;Every time you write the word &lt;code&gt;RUN&lt;/code&gt; in a Dockerfile, Docker creates a brand new "layer" (a snapshot) of your system. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two &lt;code&gt;RUN&lt;/code&gt; commands&lt;/strong&gt; = Two layers added to your image. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One &lt;code&gt;RUN&lt;/code&gt; command with &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;&lt;/strong&gt; = One single layer. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By chaining related commands together with &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, you tell Docker to execute both actions simultaneously inside the exact same layer. This reduces your final image size, speeds up build times, and is the golden standard for production Dockerfiles!&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Creating the folder required to run nginx properly.
&lt;/h4&gt;

&lt;p&gt;We need to create the &lt;code&gt;/run/nginx&lt;/code&gt; folder. Because&lt;br&gt;
When Nginx boots up, the operating system assigns it a unique ID number. Nginx is hardcoded to instantly write this number into a file called &lt;code&gt;nginx.pid&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;By default, it tries to save this file in a locked, root-only system folder. Because we are securing our app by running as a non-root user, Nginx will instantly crash with a "Permission Denied" error. We pre-create the &lt;code&gt;/run/nginx&lt;/code&gt; folder to give our app a dedicated, writeable sandbox just for this one file.&lt;/p&gt;

&lt;p&gt;Add the mkdir command below after the above line&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;RUN mkdir -p /run/nginx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Setting the owners for the folders which required protection
&lt;/h4&gt;

&lt;p&gt;For securing our frontend app. We need to provide protection mostly to the &lt;strong&gt;frontend-app&lt;/strong&gt; and some nginx folders.&lt;/p&gt;

&lt;p&gt;Then add the &lt;strong&gt;Run&lt;/strong&gt; command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;RUN chown -R root:frontend_group /frontend-app /etc/nginx /run/nginx /var/cache/nginx /var/log/nginx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;RUN&lt;/code&gt;: Tells Docker to execute this command while building the image.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chown&lt;/code&gt; (Change Owner): This is the Linux command used to change who owns a file or folder.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-R&lt;/code&gt; (Recursive): Just like in our chmod commands, this tells Linux to dive into the folders and apply this ownership to every single file and sub-folder hidden inside them.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;root:frontend_group&lt;/code&gt;: This is the core logic. You are explicitly stating two things:&lt;/li&gt;
&lt;li&gt;The Owner is &lt;code&gt;root&lt;/code&gt;: The system administrator owns the files.&lt;/li&gt;
&lt;li&gt;The Group is &lt;code&gt;frontend_group&lt;/code&gt;: You are assigning these files to your application's service group.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why do it this way? By making the root owner, you permanently prevent the frontend_user from ever running a chmod command to change permissions later.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Folders (Why these specific five?)
&lt;/h4&gt;

&lt;p&gt;When you run Nginx as a standard, non-root user (like &lt;code&gt;frontend_user&lt;/code&gt;), you have to manually grant it access to the specific folders it relies on to function. If Nginx cannot read or write to these native Linux folders, the container will instantly crash with a "Permission Denied" error on startup.&lt;/p&gt;

&lt;p&gt;Here is exactly what Nginx is doing in each of those directories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;/frontend-app&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it is:&lt;/strong&gt; Your custom directory containing your compiled React/Vite build (the &lt;code&gt;dist&lt;/code&gt; folder).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it needs it:&lt;/strong&gt; Nginx needs permission to open this folder to serve your HTML, CSS, and JS files to the user's browser.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;/etc/nginx&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it is:&lt;/strong&gt; The default Linux directory for Nginx configuration files (like &lt;code&gt;nginx.conf&lt;/code&gt; and your custom server blocks).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it needs it:&lt;/strong&gt; Nginx has to read these rules to know how to route traffic, handle API proxies, and serve your application.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;/run/nginx&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it is:&lt;/strong&gt; A temporary system folder used to store the PID (Process ID) file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it needs it:&lt;/strong&gt; When Nginx starts, Linux assigns it a unique ID number and writes that number to a file (e.g., &lt;code&gt;nginx.pid&lt;/code&gt;). If Nginx cannot create this file, it refuses to start.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;/var/cache/nginx&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it is:&lt;/strong&gt; The storage location for temporary files and cached data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it needs it:&lt;/strong&gt; When Nginx processes large client requests, handles fastCGI, or acts as a proxy, it buffers that data into temporary files. It needs strict Write access here to function efficiently.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;/var/log/nginx&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it is:&lt;/strong&gt; The official directory for &lt;code&gt;access.log&lt;/code&gt; (who visited your site) and &lt;code&gt;error.log&lt;/code&gt; (what broke).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it needs it:&lt;/strong&gt; Nginx constantly streams data to these files while running. If it cannot write to the log folder, it will crash.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As we created a directory &lt;code&gt;/run/nginx&lt;/code&gt;. For nginx to write its PID (Process ID). &lt;/p&gt;

&lt;p&gt;By default, Nginx is hardcoded to write this file into a strict, root-only system folder. Because we are forcing Nginx to run as our non-root &lt;code&gt;frontend_user&lt;/code&gt;, it will instantly crash with a "Permission Denied" error when it tries to write that PID file.&lt;/p&gt;

&lt;p&gt;To fix this, we use a single command to rewrite the Nginx configuration file and point the PID to the &lt;code&gt;/run/nginx&lt;/code&gt; folder we explicitly unlocked in the previous step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Update the PID path in the nginx config&lt;/span&gt;
 RUN sed -i 's|.*nginx.pid;|pid /run/nginx/nginx.pid;|g' /etc/nginx/nginx.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This line changes the &lt;code&gt;nginx&lt;/code&gt; internal configuration command. It changes the default path from &lt;code&gt;nginx.pid&lt;/code&gt; to &lt;code&gt;/run/nginx/nginx.pid&lt;/code&gt;. &lt;/p&gt;

&lt;h4&gt;
  
  
  5. Locking down our files and folders. And give the least privilege to the groups and users.
&lt;/h4&gt;

&lt;p&gt;We have already seen the five folders above. We need to lock some files and folders among them.&lt;/p&gt;

&lt;p&gt;For our two core directories—&lt;code&gt;/frontend-app&lt;/code&gt; (our code) and &lt;code&gt;/etc/nginx&lt;/code&gt; (our config)—we give root full control. However, we restrict our frontend_group to only Read and Execute permissions. This allows Nginx to open the folders and serve the website perfectly, but physically prevents it from modifying, overwriting, or deleting any core files.&lt;/p&gt;

&lt;p&gt;For locking down our core folder, use the &lt;strong&gt;chmod&lt;/strong&gt; command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 750 /frontend-app /etc/nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For our main application code, we used &lt;code&gt;750&lt;/code&gt; to strictly block write access. However, Nginx physically &lt;em&gt;must&lt;/em&gt; be able to write to its cache, logs, and PID directories to function.&lt;/p&gt;

&lt;p&gt;We unlock these specific, isolated sandbox folders using the &lt;code&gt;770&lt;/code&gt; lock code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Grant Write access to the isolated Nginx runtime folders&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 770 /run/nginx /var/cache/nginx /var/log/nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-R&lt;/code&gt; (Recursive):&lt;/strong&gt; Apply this permission to the folders and everything inside them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;770&lt;/code&gt; (The Access Matrix):&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Owner gets &lt;code&gt;7&lt;/code&gt; (4 + 2 + 1):&lt;/strong&gt; &lt;code&gt;root&lt;/code&gt; retains full control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group gets &lt;code&gt;7&lt;/code&gt; (4 + 2 + 1):&lt;/strong&gt; Our &lt;code&gt;frontend_group&lt;/code&gt; (Nginx) is granted Read, &lt;strong&gt;Write&lt;/strong&gt;, and Execute powers. This is the crucial difference. We are explicitly allowing Nginx to generate and delete files inside these sandboxed directories.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Others get &lt;code&gt;0&lt;/code&gt;:&lt;/strong&gt; Unauthenticated users are completely blocked.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By specifically targeting only these runtime directories with &lt;code&gt;770&lt;/code&gt;, we give Nginx exactly the Write permissions it needs to survive, without exposing our core application code to dangerous modifications!&lt;/p&gt;

&lt;p&gt;That's it. Now let's switch from &lt;code&gt;root&lt;/code&gt; to &lt;code&gt;frontend_user&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Add the below command just before the expose command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; frontend_user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After all the above changes, your Dockerfile looks like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:lts-alpine3.22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /frontend-app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;package.json,target&lt;span class="o"&gt;=&lt;/span&gt;package.json &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;package-lock.json,target&lt;span class="o"&gt;=&lt;/span&gt;package-lock.json &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.npm &lt;span class="se"&gt;\
&lt;/span&gt;    npm ci

&lt;span class="c"&gt;# Copy the rest of the source files into the image.&lt;/span&gt;

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

&lt;span class="c"&gt;# Run the build script.&lt;/span&gt;

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

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;nginx:stable-alpine-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;final&lt;/span&gt;


&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; nginx-entrypoint.sh /frontend-app/nginx-entrypoint.sh&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/\r$//'&lt;/span&gt; /frontend-app/nginx-entrypoint.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /frontend-app/nginx-entrypoint.sh

&lt;span class="c"&gt;# Copy the production dependencies from the build stage&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /frontend-app/nginx.conf /etc/nginx/conf.d/default.conf&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /frontend-app/dist /frontend-app/mywebsite&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-S&lt;/span&gt; frontend_group &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="nt"&gt;-S&lt;/span&gt; frontend_user &lt;span class="nt"&gt;-G&lt;/span&gt; frontend_group
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /run/nginx
&lt;span class="c"&gt;# # 1. Set root as the owner and frontend_group as the group for ALL folders.&lt;/span&gt;
&lt;span class="c"&gt;# # Because root owns them, frontend_user is permanently blocked from using chmod.&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; root:frontend_group /frontend-app /etc/nginx /run/nginx /var/cache/nginx /var/log/nginx
&lt;span class="c"&gt;# # 2. Update the PID path in the nginx config&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s|.*nginx.pid;|pid /run/nginx/nginx.pid;|g'&lt;/span&gt; /etc/nginx/nginx.conf
&lt;span class="c"&gt;# # 3. Lock down the app and config folders to Read/Execute (750).&lt;/span&gt;
&lt;span class="c"&gt;# # Owner (root): Read/write/Execute (7)&lt;/span&gt;
&lt;span class="c"&gt;# # Group (frontend_group): Read/Execute (5)&lt;/span&gt;
&lt;span class="c"&gt;# # Others: No Access (0)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 750 /frontend-app /etc/nginx
&lt;span class="c"&gt;# # 4. Grant Write access to the specific runtime folders Nginx needs to function and env creation folder (770).&lt;/span&gt;
&lt;span class="c"&gt;# # Owner (root): Read/Write/Execute (7)&lt;/span&gt;
&lt;span class="c"&gt;# # Group (frontend_group): Read/Write/Execute (7)&lt;/span&gt;
&lt;span class="c"&gt;# # Others: No Access (0)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 770 /run/nginx /var/cache/nginx /var/log/nginx
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; frontend_user&lt;/span&gt;

&lt;span class="c"&gt;# Expose the port that the application listens on.&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/frontend-app/nginx-entrypoint.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's build our Docker image and then run it&lt;/p&gt;

&lt;p&gt;For building run inside the &lt;strong&gt;frontend&lt;/strong&gt; directory&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; my-frontend-secure-todo-multistage:v1 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Now, let's run our container. But before that, please delete your older frontend container that we created previously.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--env-file&lt;/span&gt; ./.env &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="nt"&gt;--name&lt;/span&gt; todo-app-multistage-secure-container my-frontend-secure-todo-multistage:v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running the above command, it runs successfully&lt;/p&gt;

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

&lt;p&gt;Now go to our frontend app and refresh it, or open the URL &lt;br&gt;
&lt;code&gt;http://localhost:8080&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;We are not able to see any todos in our app. Check if your backend server is still running. In my case, it's running. Also, our frontend again starts calling the &lt;strong&gt;undefined&lt;/strong&gt; URL. This means we again messed up with &lt;strong&gt;env&lt;/strong&gt; variables. But where 🤔? &lt;/p&gt;

&lt;p&gt;Check the nginx logs in the terminal where you run the &lt;code&gt;docker run&lt;/code&gt; command. If you check the starting logs. You will see some &lt;strong&gt;Permission Denied&lt;/strong&gt; errors.&lt;/p&gt;

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

&lt;p&gt;What's the issue here?&lt;/p&gt;

&lt;p&gt;If you remember, for injecting the runtime environment. We are creating a &lt;code&gt;/env-config.js&lt;/code&gt;. On the startup of our container inside the directory &lt;code&gt;/frontend-app/mywebsite&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So when we start our container with &lt;code&gt;frontend_user&lt;/code&gt;. It had only read and execute permission for the directory &lt;code&gt;/frontend-app/mywebsite&lt;/code&gt;. But here we are trying to create a new file inside the &lt;code&gt;/frontend-app/mywebsite&lt;/code&gt;. Due to a lack of write permission, the creation of the &lt;code&gt;/env-config.js&lt;/code&gt; file failed. And in the terminal, it's showing a permission denied error. How can we fix it?&lt;/p&gt;

&lt;p&gt;I still don't want to give write permission to &lt;code&gt;frontend_user&lt;/code&gt; for &lt;code&gt;/frontend-app/mywebsite&lt;/code&gt; directory. Otherwise, we again get into trouble. &lt;/p&gt;

&lt;p&gt;But I can create one directory inside the &lt;code&gt;/frontend-app/mywebsite&lt;/code&gt;. Specially for creating the&lt;code&gt;/env-config.js&lt;/code&gt; file. And permit write permission only for that particular directory instead of the whole &lt;code&gt;/frontend-app/mywebsite&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;So we create a &lt;code&gt;/env&lt;/code&gt; directory inside the &lt;code&gt;/frontend-app/mywebsite&lt;/code&gt; directory. And then give write permission to our &lt;code&gt;frontend_user&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For creating &lt;code&gt;/env&lt;/code&gt;, add the &lt;strong&gt;mkdir&lt;/strong&gt; command. Just after this line &lt;code&gt;RUN mkdir -p /run/nginx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /frontend-app/mywebsite/env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, to give write permission, just add the above directory at the end of the command.&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;/frontend-app/mywebsite/env&lt;/code&gt; at the end of the &lt;strong&gt;chmod&lt;/strong&gt; command like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 770 /run/nginx /var/cache/nginx /var/log/nginx /frontend-app/mywebsite/env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is one more problem we need to address&lt;/p&gt;

&lt;p&gt;The Port 80 Problem: Privileged vs. Unprivileged Ports&lt;/p&gt;

&lt;p&gt;If you try to run your secure, non-root Nginx container on standard HTTP Port 80, it will immediately crash. But why?&lt;/p&gt;

&lt;p&gt;In Linux, network ports are divided into two strict categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privileged Ports (0 to 1023):&lt;/strong&gt; These include core internet protocols like HTTP (80), HTTPS (443), and SSH (22). &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unprivileged Ports (1024 to 65535):&lt;/strong&gt; These are open for general application use.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Historically, Linux restricted ports under 1024 as a security measure. It guaranteed users that if a website was running on Port 80, it was officially set up by the system administrator (&lt;code&gt;root&lt;/code&gt;), not a random user running a rogue script.&lt;/p&gt;

&lt;p&gt;Because we intentionally stripped away our administrative powers by using &lt;code&gt;USER frontend_user&lt;/code&gt;, Linux mathematically prevents our application from binding to Port 80. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt; Instead of fighting Linux security, we follow the industry standard: we tell Nginx to run on an &lt;em&gt;unprivileged&lt;/em&gt; port, like &lt;strong&gt;8080&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So, in your Dockerfile, change &lt;code&gt;EXPOSE 80&lt;/code&gt; to &lt;code&gt;EXPOSE 8080&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, after all changes, your Dockerfile looks like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# FROM node:lts-alpine3.22&lt;/span&gt;

&lt;span class="c"&gt;# # Install nginx on Apline Linux&lt;/span&gt;
&lt;span class="c"&gt;# RUN apk add --no-cache nginx&lt;/span&gt;

&lt;span class="c"&gt;# WORKDIR /frontend-app&lt;/span&gt;

&lt;span class="c"&gt;# COPY package*.json ./&lt;/span&gt;
&lt;span class="c"&gt;# RUN npm ci&lt;/span&gt;

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

&lt;span class="c"&gt;# # Build arguments for environment variables&lt;/span&gt;
&lt;span class="c"&gt;# # ARG VITE_API_URL&lt;/span&gt;
&lt;span class="c"&gt;# # ENV VITE_API_URL=$VITE_API_URL&lt;/span&gt;

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

&lt;span class="c"&gt;# # Ensure nginx directories exist&lt;/span&gt;
&lt;span class="c"&gt;# RUN mkdir -p /frontend-app/mywebsite&lt;/span&gt;
&lt;span class="c"&gt;# COPY nginx-entrypoint.sh /frontend-app/nginx-entrypoint.sh&lt;/span&gt;
&lt;span class="c"&gt;# RUN sed -i 's/\r$//' /frontend-app/nginx-entrypoint.sh&lt;/span&gt;
&lt;span class="c"&gt;# RUN chmod +x /frontend-app/nginx-entrypoint.sh&lt;/span&gt;

&lt;span class="c"&gt;# # Copy nginx config&lt;/span&gt;

&lt;span class="c"&gt;# COPY nginx.conf /etc/nginx/http.d/default.conf&lt;/span&gt;


&lt;span class="c"&gt;# # Move dist files&lt;/span&gt;
&lt;span class="c"&gt;# RUN cp -r ./dist/* /frontend-app/mywebsite/&lt;/span&gt;

&lt;span class="c"&gt;# EXPOSE 80&lt;/span&gt;
&lt;span class="c"&gt;# # CMD ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;span class="c"&gt;# ENTRYPOINT ["/frontend-app/nginx-entrypoint.sh"]&lt;/span&gt;


&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:lts-alpine3.22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /frontend-app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;package.json,target&lt;span class="o"&gt;=&lt;/span&gt;package.json &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;package-lock.json,target&lt;span class="o"&gt;=&lt;/span&gt;package-lock.json &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.npm &lt;span class="se"&gt;\
&lt;/span&gt;    npm ci

&lt;span class="c"&gt;# Copy the rest of the source files into the image.&lt;/span&gt;

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

&lt;span class="c"&gt;# Run the build script.&lt;/span&gt;

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

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;nginx:stable-alpine-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;final&lt;/span&gt;


&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; nginx-entrypoint.sh /frontend-app/nginx-entrypoint.sh&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/\r$//'&lt;/span&gt; /frontend-app/nginx-entrypoint.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /frontend-app/nginx-entrypoint.sh

&lt;span class="c"&gt;# Copy the production dependencies from the build stage&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /frontend-app/nginx.conf /etc/nginx/conf.d/default.conf&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /frontend-app/dist /frontend-app/mywebsite&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-S&lt;/span&gt; frontend_group &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="nt"&gt;-S&lt;/span&gt; frontend_user &lt;span class="nt"&gt;-G&lt;/span&gt; frontend_group
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /run/nginx
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /frontend-app/mywebsite/env
&lt;span class="c"&gt;# 1. Set root as the owner and frontend_group as the group for ALL folders.&lt;/span&gt;
&lt;span class="c"&gt;# Because root owns them, frontend_user is permanently blocked from using chmod.&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; root:frontend_group /frontend-app /etc/nginx /run/nginx /var/cache/nginx /var/log/nginx
&lt;span class="c"&gt;# 2. Update the PID path in the nginx config&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s|.*nginx.pid;|pid /run/nginx/nginx.pid;|g'&lt;/span&gt; /etc/nginx/nginx.conf
&lt;span class="c"&gt;# 3. Lock down the app and config folders to Read/Execute (750).&lt;/span&gt;
&lt;span class="c"&gt;# Owner (root): Read/write/Execute (7)&lt;/span&gt;
&lt;span class="c"&gt;# Group (frontend_group): Read/Execute (5)&lt;/span&gt;
&lt;span class="c"&gt;# Others: No Access (0)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 750 /frontend-app /etc/nginx
&lt;span class="c"&gt;# 4. Grant Write access to the specific runtime folders Nginx needs to function and env creation folder (770).&lt;/span&gt;
&lt;span class="c"&gt;# Owner (root): Read/Write/Execute (7)&lt;/span&gt;
&lt;span class="c"&gt;# Group (frontend_group): Read/Write/Execute (7)&lt;/span&gt;
&lt;span class="c"&gt;# Others: No Access (0)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 770 /run/nginx /var/cache/nginx /var/log/nginx /frontend-app/mywebsite/env
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; frontend_user&lt;/span&gt;

&lt;span class="c"&gt;# Expose the port that the application listens on.&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/frontend-app/nginx-entrypoint.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, update your &lt;code&gt;nginx.conf&lt;/code&gt; file. With the following changes&lt;br&gt;
change these two lines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From &lt;code&gt;listen 80&lt;/code&gt;; to &lt;code&gt;listen 8080&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;From &lt;code&gt;listen [::]:80;&lt;/code&gt; to &lt;code&gt;listen [::]:8080;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, your &lt;code&gt;nginx.cong&lt;/code&gt;. Files look like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;   &lt;span class="n"&gt;server&lt;/span&gt;{
    &lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;;          &lt;span class="c"&gt;# Listen for IPv4
&lt;/span&gt;    &lt;span class="n"&gt;listen&lt;/span&gt; [::]:&lt;span class="m"&gt;8080&lt;/span&gt;;     &lt;span class="c"&gt;# Listen for IPv6
&lt;/span&gt;    &lt;span class="n"&gt;server_name&lt;/span&gt; &lt;span class="n"&gt;localhost&lt;/span&gt;;

    &lt;span class="n"&gt;root&lt;/span&gt; /&lt;span class="n"&gt;frontend&lt;/span&gt;-&lt;span class="n"&gt;app&lt;/span&gt;/&lt;span class="n"&gt;mywebsite&lt;/span&gt;;
    &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt;;


    &lt;span class="n"&gt;location&lt;/span&gt; / {
        &lt;span class="n"&gt;try_files&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt; /&lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt;;
    }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, update the new env path in &lt;code&gt;nginx-entrypoint.sh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Update the &lt;strong&gt;CONFIG_PATH&lt;/strong&gt; from &lt;code&gt;/frontend-app/mywebsite/env-config.js&lt;/code&gt; to &lt;code&gt;/frontend-app/mywebsite/env/env-config.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After the change, your final &lt;code&gt;nginx-entrypoint.sh&lt;/code&gt; file looks like&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c"&gt;# Start the generated config file&lt;/span&gt;
&lt;span class="nv"&gt;CONFIG_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/frontend-app/mywebsite/env/env-config.js"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"window.env = {"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$CONFIG_PATH&lt;/span&gt;

&lt;span class="c"&gt;# Find all environment variables starting with "VITE_" and add them to the file&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;var &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;env&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"VITE_"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$var&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"="&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; 1&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$var&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"="&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; 2-&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  &lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$CONFIG_PATH&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# End the config file&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"};"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$CONFIG_PATH&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Environment variable injection completed."&lt;/span&gt;
&lt;span class="nb"&gt;exec &lt;/span&gt;nginx &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="s1"&gt;'daemon off;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, change the &lt;strong&gt;src&lt;/strong&gt; path in our &lt;code&gt;index.html&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;From &lt;code&gt;&amp;lt;script async src="/env-config.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt; to&lt;br&gt;
&lt;code&gt;&amp;lt;script async src="/env/env-config.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After the above changes, your &lt;code&gt;index.html&lt;/code&gt; file looks like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/svg+xml"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/vite.svg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/env/env-config.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Vite + React + TS&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"root"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/src/main.tsx"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're done with all the changes. Now, let's build it again and run it to check if everything is working or not&lt;/p&gt;

&lt;p&gt;For building run &lt;strong&gt;docker build&lt;/strong&gt; inside the &lt;strong&gt;frontend&lt;/strong&gt; directory&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; my-frontend-secure-todo-multistage:v2 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It build successfully&lt;/p&gt;

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

&lt;p&gt;Now start the container. But please delete the older container that we created previously.&lt;/p&gt;

&lt;p&gt;Then run the &lt;strong&gt;docker run&lt;/strong&gt; command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--env-file&lt;/span&gt; ./.env &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="nt"&gt;--name&lt;/span&gt; todo-app-multistage-secure-container my-frontend-secure-todo-multistage:v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our container started without any &lt;strong&gt;Permission denied&lt;/strong&gt; error.&lt;/p&gt;

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

&lt;p&gt;Now go to the URL &lt;code&gt;http://localhost:8080&lt;/code&gt;. And check now we getting todos or not?&lt;/p&gt;

&lt;p&gt;Hurray, it's working 😎&lt;/p&gt;

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

&lt;p&gt;Now let's check if our app is still secure or not. Now we again try to perform the same scenario that we performed previously. We try to &lt;strong&gt;delete&lt;/strong&gt; some files inside the &lt;code&gt;mywebsite&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;To go inside the container, run the &lt;strong&gt;docker exec&lt;/strong&gt; command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; todo-app-multistage-secure-container sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Windows run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;winpty docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; todo-app-multistage-secure-container sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you're inside the container&lt;/p&gt;

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

&lt;p&gt;Run the &lt;strong&gt;whoami&lt;/strong&gt; command, and you will see &lt;code&gt;frontend_user&lt;/code&gt; after running it.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;You will see&lt;/p&gt;

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

&lt;p&gt;Now go to the &lt;code&gt;/frontend-app/mywebsite&lt;/code&gt; directory. And try to delete the &lt;code&gt;index.html&lt;/code&gt; file. You will see a permission denied error. &lt;code&gt;frontend_user&lt;/code&gt; is not able to delete the &lt;code&gt;index.html&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Run the &lt;strong&gt;cd&lt;/strong&gt; command to go inside the &lt;code&gt;/frontend-app/mywebsite&lt;/code&gt;directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /frontend-app/mywebsite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Then run the &lt;strong&gt;rm -rf&lt;/strong&gt; command for deleting the &lt;code&gt;index.html&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see a permission denied error&lt;/p&gt;

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

&lt;p&gt;See our &lt;code&gt;frontend_user&lt;/code&gt; working fine, it's unable to delete any files inside the &lt;code&gt;frontend-app&lt;/code&gt;. But still it can delete &lt;code&gt;/env/env-config.js&lt;/code&gt; file because it had write access for the &lt;code&gt;/env&lt;/code&gt; directory. You can try deleting on your end.&lt;/p&gt;

&lt;p&gt;So we secured our website from being deleted. Now the hacker is not able to delete our website, at least. &lt;/p&gt;

&lt;p&gt;So finally we've done it. And we successfully created our production-ready frontend Dockerfile. Now you can use the same template for any frontend project which mostly based on CSR.&lt;/p&gt;

&lt;p&gt;That's all for this blog post. We're done with our blog post. If you want to check out the bonus section. Go and check it for extra knowledge.&lt;/p&gt;

&lt;p&gt;And if you follow along till here, pat your back. You did a great job.&lt;/p&gt;

&lt;h3&gt;
  
  
  12. &lt;strong&gt;Bonus:&lt;/strong&gt; Extra observation on nginx.&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/uCDVp6y5-aE" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Note:&lt;/p&gt;

&lt;p&gt;The core tutorial concludes here. The following section covers some extra observations of mine on &lt;strong&gt;nginx&lt;/strong&gt;. It is strictly optional—feel free to skip it, or read on to understand the underlying mechanics.&lt;/p&gt;

&lt;p&gt;If you noticed or not. The path to &lt;strong&gt;default.conf&lt;/strong&gt; in the &lt;strong&gt;Dockerfile&lt;/strong&gt; changes depending on the image.&lt;/p&gt;

&lt;p&gt;In the following Dockerfile, we used a different path to replace the existing &lt;strong&gt;default.conf&lt;/strong&gt; with our custom &lt;strong&gt;nginx.conf&lt;/strong&gt; file.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;node:lts&lt;/strong&gt; image there we used the path &lt;code&gt;/etc/nginx/sites-available/default&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The line we used in our Dockerfile &lt;code&gt;COPY nginx.conf /etc/nginx/sites-available/default&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;node:lts-alpine3.22&lt;/strong&gt; we used &lt;code&gt;/etc/nginx/http.d/nginx.conf&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In the final Stage of the multistage Dockerfile for the image &lt;strong&gt;nginx:stable-alpine-slim&lt;/strong&gt;. &lt;br&gt;
We used path &lt;code&gt;/etc/nginx/conf.d/default.conf&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Every time &lt;strong&gt;default.conf&lt;/strong&gt; file path location changes on each Docker file. Why? Because each Linux distro uses a different location for &lt;strong&gt;niginx&lt;/strong&gt; configuration files.&lt;/p&gt;

&lt;p&gt;How to know about these paths?&lt;/p&gt;

&lt;p&gt;To check it if you're not sure about the path.&lt;/p&gt;

&lt;p&gt;Just install &lt;strong&gt;nginx&lt;/strong&gt; and run the container with the default configuration. Now go inside the container via the &lt;code&gt;exec&lt;/code&gt; command. And then run the &lt;code&gt;nginx -t&lt;/code&gt; command. It will give you the location of the main nginx configuration file. Now run the &lt;code&gt;cat&lt;/code&gt; command to check the content of &lt;strong&gt;nginx.conf&lt;/strong&gt; main file content.&lt;/p&gt;

&lt;p&gt;Let’s do this as well. Do it in our running container. We just ran the container for our final build&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; todo-app-multistage-container /bin/sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will connect your terminal to the container. Once you are inside your container. Run below command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;niginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Copy the highlighted path and run the command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/nginx/nginx.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Are you able to see the content of &lt;strong&gt;niginx.conf&lt;/strong&gt; file. In that file, check for an &lt;strong&gt;include&lt;/strong&gt; statement. It will denote the path where you can put your site config files.&lt;/p&gt;

&lt;p&gt;That’s it.&lt;/p&gt;

&lt;h3&gt;
  
  
  13. &lt;strong&gt;Wrap Up:&lt;/strong&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;We're done with our frontend Docker image part. We have seen all the aspects of building images. Now you can also optimize our prod build further by using distroless images. And learn about it more. Stay tuned for the next post, where we will containerize our backend and integrate a MySQL database.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>backend</category>
      <category>devops</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>How to Containerize a Full-Stack App (Part 3)-Containerizing the Backend and Mysql Database Integration</title>
      <dc:creator>utkarsh srivastava</dc:creator>
      <pubDate>Sun, 01 Nov 2026 05:54:15 +0000</pubDate>
      <link>https://dev.to/utkarsh4321/how-to-containerize-a-full-stack-app-part-3-containerizing-the-backend-and-mysql-database-3kc8</link>
      <guid>https://dev.to/utkarsh4321/how-to-containerize-a-full-stack-app-part-3-containerizing-the-backend-and-mysql-database-3kc8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Series Navigation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Part 1:&lt;/strong&gt; &lt;a href="https://dev.to/utkarsh4321/how-to-containerize-a-full-stack-app-part-1-setting-up-the-local-development-environment-18ee"&gt;&lt;strong&gt;Setting Up Locally&lt;/strong&gt;&lt;/a&gt; &lt;em&gt;(Required)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Part 2:&lt;/strong&gt; &lt;a href="https://dev.to/utkarsh4321/how-to-containerize-a-full-stack-app-part-2-optimizing-the-frontend-with-multi-stage-docker-1cbo-temp-slug-814588"&gt;&lt;strong&gt;Frontend Multi-Stage Docker Builds&lt;/strong&gt;&lt;/a&gt; &lt;em&gt;(Optional for this guide)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;📍 &lt;strong&gt;Part 3:&lt;/strong&gt; &lt;strong&gt;Containerizing the Backend &amp;amp; MySQL&lt;/strong&gt; &lt;em&gt;(You are here)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is &lt;strong&gt;Part 3&lt;/strong&gt; of our series. If you haven't set up the local Full-Stack Todo app yet, check out &lt;a href="https://dev.to/utkarsh4321/part1-how-to-containerize-a-full-stack-app-react-express-and-mysql-4coi-temp-slug-660007/preview"&gt;&lt;strong&gt;Part 1 Setting Up Locally&lt;/strong&gt;&lt;/a&gt; to get your environment ready.&lt;br&gt;
If you do not containerize the frontend app, check out &lt;a href="https://dev.to/utkarsh4321/how-to-containerize-a-full-stack-app-part-2-optimizing-the-frontend-with-multi-stage-docker-1cbo-temp-slug-814588/preview"&gt;&lt;strong&gt;Part 2 Frontend Multi-Stage Docker Builds&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to skip both, follow  TL;DR: Fast-Track Setup section.&lt;/p&gt;


&lt;h2&gt;
  
  
  TL;DR: Fast-Track Setup
&lt;/h2&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Prerequisite:&lt;/strong&gt; Before starting this step, you must have &lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt; and &lt;strong&gt;Node.js&lt;/strong&gt; installed and running on your machine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you didn't follow along with the previous posts or simply want to skip the manual local setup, you can easily catch up. Grab the existing code from the repository and spin it up using Docker to jump right into the rest of the content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Clone the repository and checkout the &lt;code&gt;part-2-frontend&lt;/code&gt; branch:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check out the repo &lt;a href="https://github.com/utkarsh4321/devops-todo-starter-app.git" rel="noopener noreferrer"&gt;link&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/utkarsh4321/devops-todo-starter-app.git
&lt;span class="nb"&gt;cd &lt;/span&gt;devops-todo-starter-app
git checkout part-2-frontend
&lt;span class="nb"&gt;cd &lt;/span&gt;frontend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Build the Docker image:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; my-frontend-secure-todo-multistage:v2 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Run the container:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--env-file&lt;/span&gt; ./.env &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="nt"&gt;--name&lt;/span&gt; todo-app-multistage-secure-container my-frontend-secure-todo-multistage:v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Once your container is up and running on port &lt;code&gt;8080&lt;/code&gt;, validate frontend in the browser.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Set up MySQL container:&lt;/strong&gt;&lt;br&gt;
In the terminal run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--name&lt;/span&gt; mysql-container &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;myrootpassword &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my_testing_db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3306:3306 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; mysql:8.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Set up backend environment:&lt;/strong&gt;&lt;br&gt;
Inside the backend folder, create a .env file. Copy the environment variables from below and paste them&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=my_testing_db
DB_USERNAME=root
DB_PASSWORD=myrootpassword
ALLOWED_ORIGIN=http://localhost:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Run the backend:&lt;/strong&gt;&lt;br&gt;
Open a new terminal and run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;backend
npm i
npm run migrate
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Once your backend is up and running on port &lt;code&gt;5500&lt;/code&gt;, you are completely caught up! Let's continue with the rest of the blog post.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Intro
&lt;/h3&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/HxTYGllg2I0" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Part 3&lt;/strong&gt;, we will guide you on how to containerize our &lt;strong&gt;Backend&lt;/strong&gt; application with a &lt;strong&gt;MySQL&lt;/strong&gt; database.&lt;/p&gt;

&lt;p&gt;If you are a &lt;strong&gt;backend&lt;/strong&gt; developer and only want to know how to Dockerize &lt;strong&gt;backend&lt;/strong&gt;, this section is very important.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here is what we will cover:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Backend Code Walkthrough:&lt;/strong&gt; A brief overview of our Backend app structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generating the Production Build:&lt;/strong&gt; How to prepare the app for deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment Architecture:&lt;/strong&gt; High-level view of our deployment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writing multistage dockerfile for our backend:&lt;/strong&gt; Writing the step-by-step build instructions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Building Docker Image:&lt;/strong&gt; Building Docker Image and Running it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persisting MySQL Data in Docker Containers:&lt;/strong&gt; Secure your MySQL data against container destruction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolating Services with Docker Networks:&lt;/strong&gt; Use custom internal networks to securely connect your backend to MySQL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automating with Docker Compose:&lt;/strong&gt; Spin up your complete architecture, including networks and volumes, with just one command.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bonus Section:&lt;/strong&gt; Add a React frontend to complete your stack.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wrap Up&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Backend Code Walkthrough:&lt;/strong&gt; A brief overview of our Backend app structure.&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/AXGO0qlLS-c" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Our Backend Tech stack is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Express js &lt;/li&gt;
&lt;li&gt;Drizzle ORM&lt;/li&gt;
&lt;li&gt;Esbuild as a bundler&lt;/li&gt;
&lt;li&gt;Mysql DB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our backend file structure looks like &lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backend/
├── drizzle/   
├── node_modules/
├── routes/
├── .env
├── .env-example
├── .gitignore
├── db.js
├── drizzle.config.js
├── esbuild.config.js
├── index.js
├── migrate.js
├── package-lock.json
├── package.json
└── schema.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation of each files and folders&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;drizzle/&lt;/strong&gt;: Stores database migration files and snapshots generated by Drizzle ORM to track schema changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;node_modules/&lt;/strong&gt;: Contains all the installed third-party libraries and dependencies required for the project to run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;routes/&lt;/strong&gt;:  Define routes for the todos endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;db.js&lt;/strong&gt;: likely the setup file that initializes and exports the connection to our database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;drizzle.config.js:&lt;/strong&gt; The configuration file for Drizzle Kit, telling it where to find your schema and where to output migrations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;index.js&lt;/strong&gt;: The entry point for our backend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;migrate.js&lt;/strong&gt;: A custom script to run migrations. With some dummy-seeded todos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;package-lock.json&lt;/strong&gt;: Locks down the exact versions of your dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;package.json&lt;/strong&gt;: The project manifest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;schema.js&lt;/strong&gt;: Defines your database tables and columns using Drizzle syntax.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;esbuild.config.js&lt;/strong&gt;: Configuration for esbuild. Using this to bundle your backend code for deployment or to transpile TypeScript/modern JS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Generating the Production Build:&lt;/strong&gt; How to prepare the app for deployment.&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/NFk3J2OHpDY" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Our backend project required the following steps for deployment&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Database migration&lt;/li&gt;
&lt;li&gt;Build creation&lt;/li&gt;
&lt;li&gt;Securely providing env variables&lt;/li&gt;
&lt;li&gt;A health check that ensures that the DB and backend are running successfully.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The 3rd and 4th points we see later. For 1st and 2nd points, we need to check our &lt;strong&gt;package.json&lt;/strong&gt; file.&lt;/p&gt;

&lt;p&gt;Our &lt;strong&gt;package.json&lt;/strong&gt; looks like&lt;/p&gt;

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

&lt;p&gt;In the script section, we need to focus on these commands:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;generate&lt;/strong&gt;: Script used to generate DB &lt;strong&gt;migrations&lt;/strong&gt;.&lt;br&gt;
&lt;strong&gt;migrate&lt;/strong&gt;: Script used to run migration on the database.&lt;br&gt;
&lt;strong&gt;build&lt;/strong&gt;: Script used to create a bundle.&lt;/p&gt;

&lt;p&gt;Note: Whenever the backend container starts, we need to run our migration on the DB first. Then our backend will run. Otherwise, our backend gives an error.&lt;/p&gt;

&lt;p&gt;We already ran the &lt;code&gt;npm run migrate&lt;/code&gt; command. Now, let's see how to build our backend application.&lt;/p&gt;

&lt;p&gt;Go to the backend directory and then run&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Once you run the above command. It will create a &lt;code&gt;dist&lt;/code&gt; directory inside the backend folder. This is our backend bundled code that we need for production.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Deployment Architecture:&lt;/strong&gt;High-level view of our deployment&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/seuav7W7KDo" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Let’s Understand High-Level View&lt;/p&gt;

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

&lt;p&gt;At the end of the blog post, we will achieve the architecture described above.&lt;/p&gt;

&lt;p&gt;Here we need two containers, one for our MySQL DB and another for our Backend service.&lt;/p&gt;

&lt;p&gt;Generally in prod. We expose our backend api’s but not the DB. DB is present inside the secure network that can only be accessed via our backend service.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;Writing multistage dockerfile for our backend:&lt;/strong&gt; Writing the step-by-step build instructions.&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/dIP5_e8spgw" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;We are directly creating a multistage Docker file for our backend.&lt;/p&gt;

&lt;p&gt;Create a &lt;strong&gt;Dockerfile&lt;/strong&gt; inside the &lt;strong&gt;backend&lt;/strong&gt; folder.&lt;/p&gt;

&lt;p&gt;Then, copy and paste the code snippet into the Dockerfile&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:lts-alpine3.22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# 2. Deps Stage: Install ONLY production dependencies&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;deps&lt;/span&gt;
&lt;span class="c"&gt;# Use cache mount to install fast and keep image clean&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;package.json,target&lt;span class="o"&gt;=&lt;/span&gt;package.json &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;package-lock.json,target&lt;span class="o"&gt;=&lt;/span&gt;package-lock.json &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.npm &lt;span class="se"&gt;\
&lt;/span&gt;    npm ci &lt;span class="nt"&gt;--omit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev

&lt;span class="c"&gt;# 3. Builder Stage: Install ALL deps and build&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;package.json,target&lt;span class="o"&gt;=&lt;/span&gt;package.json &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;package-lock.json,target&lt;span class="o"&gt;=&lt;/span&gt;package-lock.json &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.npm &lt;span class="se"&gt;\
&lt;/span&gt;    npm ci
&lt;span class="c"&gt;# COPY source code AFTER installing deps to preserve cache&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# 4. Final Stage: The Production Runner&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runner&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /backend-app&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-S&lt;/span&gt; backend_group &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="nt"&gt;-S&lt;/span&gt; backend_user &lt;span class="nt"&gt;-G&lt;/span&gt; backend_group

&lt;span class="c"&gt;# Copy the entrypoint script&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; docker-entrypoint.sh /backend-app/docker-entrypoint.sh&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/\r$//'&lt;/span&gt; /backend-app/docker-entrypoint.sh
&lt;span class="c"&gt;# Copy necessary files from previous stages&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps /app/node_modules /backend-app/node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/dist /backend-app&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; root:backend_group /backend-app
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 750 /backend-app

&lt;span class="c"&gt;# Run as non-root user for security&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; backend_user&lt;/span&gt;


&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 5500&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/backend-app/docker-entrypoint.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step-by-step explanation of Docker file
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Base Stage&lt;/strong&gt;&lt;br&gt;
Establishes the common foundation for later stages.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FROM node:lts-alpine3.22 AS base&lt;/code&gt;: Starts using a lightweight Alpine Linux image with Node.js installed. Name this stage &lt;strong&gt;"base"&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WORKDIR /app&lt;/code&gt;: Sets &lt;strong&gt;/app&lt;/strong&gt; as the current working directory inside the image for future commands.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Deps Stage&lt;/strong&gt;&lt;br&gt;
Focuses solely on installing production dependencies accurately and quickly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FROM base AS deps&lt;/code&gt;: Starts a new temporary stage named "deps", inheriting from the "base" stage.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RUN --mount=... npm ci --omit=dev&lt;/code&gt;: Installs only production dependencies defined in &lt;code&gt;package-lock.json&lt;/code&gt;. It uses specialized Docker "mounts" to cache downloaded packages and temporarily read dependency files without copying them permanently inside the image.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Builder Stage&lt;/strong&gt;&lt;br&gt;
Used to compile the source code (e.g., TypeScript to JavaScript).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FROM base AS builder&lt;/code&gt;: Starts another temporary stage named "builder" from the "base" stage.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RUN --mount=... npm ci&lt;/code&gt;: Installs all dependencies, including development tools needed for building (like TypeScript), using caching for speed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COPY . .&lt;/code&gt;: Copies the actual application source code from your host machine into the container's &lt;code&gt;/app&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RUN npm run build&lt;/code&gt;: Executes the build script defined in your &lt;code&gt;package.json&lt;/code&gt; to compile the code (usually outputting to a &lt;code&gt;dist&lt;/code&gt; folder).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Final Stage (Runner)&lt;/strong&gt;&lt;br&gt;
Assembles the final, clean production image.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FROM base AS runner&lt;/code&gt;: Starts the final stage from the fresh "base" image, leaving behind build tools from previous stages.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WORKDIR /backend-app&lt;/code&gt;: Sets the default working directory inside the container where all subsequent commands will be executed and files will be placed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ENV NODE_ENV=production&lt;/code&gt;: Sets an environment variable to tell Node.js to run in optimized production mode.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RUN addgroup -S backend_group &amp;amp;&amp;amp; adduser -S backend_user -G backend_group&lt;/code&gt;: Creates a dedicated system group and user to run the application securely, avoiding the risks of using the default root user.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COPY docker-entrypoint.sh /backend-app/docker-entrypoint.sh&lt;/code&gt;: Copies a custom startup script from your project into the final image.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RUN sed -i 's/\r$//' /backend-app/docker-entrypoint.sh&lt;/code&gt;: A handy quick-fix! It removes any hidden Windows line endings from your startup script so it runs flawlessly in our Linux environment.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COPY --from=deps /app/node_modules /backend-app/node_modules&lt;/code&gt;: Copies the clean production-only dependencies from the "deps" stage.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COPY --from=builder /app/dist /backend-app&lt;/code&gt;: Copies the compiled application files (the &lt;code&gt;dist&lt;/code&gt; folder) from the "builder" stage.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RUN chown -R root:backend_group /backend-app&lt;/code&gt;: Safely assigns ownership of your app files. The root user stays in charge, but our new &lt;code&gt;backend group&lt;/code&gt; gets the access it needs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RUN chmod -R 750 /backend-app&lt;/code&gt;: Applies strict security permissions, giving the owner full control, the group read/execute rights, and blocks everyone else.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;USER backend_user&lt;/code&gt;: Switches from the default root user to the low-privilege "backend_user" user for enhanced security.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EXPOSE 5500&lt;/code&gt;: Documents that the application inside the container listens on port 5500.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ENTRYPOINT ["/backend-app/docker-entrypoint.sh"]&lt;/code&gt;: Specifies the command that runs immediately when the container starts.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We created our Docker file&lt;/p&gt;

&lt;p&gt;Also, create &lt;code&gt;docker-entrypoint.sh&lt;/code&gt; in the &lt;strong&gt;backend&lt;/strong&gt; folder. This script file will apply migrations on the database and then run the backend server.&lt;/p&gt;

&lt;p&gt;Copy and paste the given code snippet into the &lt;code&gt;docker-entrypoint.sh&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="c"&gt;# Run database migrations&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Running database migrations..."&lt;/span&gt;
npm run migrate


&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Migrations completed."&lt;/span&gt;

&lt;span class="c"&gt;# Start the Node.js application&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Starting Node.js application..."&lt;/span&gt;
node ./bundle.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The &lt;code&gt;docker-entrypoint.sh&lt;/code&gt; Script
&lt;/h3&gt;

&lt;p&gt;To ensure our container starts correctly and handles database migrations, we use a startup script. Here is the breakdown:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;#!/bin/sh&lt;/code&gt;&lt;/strong&gt;: &lt;strong&gt;Interpreter Declaration&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Tells the system to run this file using the standard, lightweight Shell (sh) interpreter.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;set -e&lt;/code&gt;&lt;/strong&gt;: &lt;strong&gt;Fail-Fast Mode&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Configures the script to exit immediately if any command returns an error. This is a best practice that prevents the app from starting in a broken state if migrations fail.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;npm run migrate&lt;/code&gt;&lt;/strong&gt;: &lt;strong&gt;Execute Migrations&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Runs the migration script defined in your &lt;code&gt;package.json&lt;/code&gt; to update your database schema to the latest version before the app starts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;node ./bundle.js&lt;/code&gt;&lt;/strong&gt;: &lt;strong&gt;Start Server&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Executes the Node.js runtime on your compiled application file. This is the main process that keeps the container alive and serving requests.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, create a &lt;code&gt;.dockerignore&lt;/code&gt; file as well inside the &lt;strong&gt;backend&lt;/strong&gt; directory.&lt;/p&gt;

&lt;p&gt;And add the following code snippet&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/.classpath&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/.dockerignore&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/.env&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/.git&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/.gitignore&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/.project&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/.settings&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/.toolstarget&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/.vs&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/.vscode&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/.next&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/.cache&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/*.*proj.user&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/*.dbmdl&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/*.jfm&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/charts&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/docker-compose*&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/compose.y*ml&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/Dockerfile*&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/node_modules&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/npm-debug.log&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/obj&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/secrets.dev.yaml&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/values.dev.yaml&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/build&lt;/span&gt;
&lt;span class="err"&gt;**&lt;/span&gt;&lt;span class="s"&gt;/dist&lt;/span&gt;
&lt;span class="s"&gt;LICENSE&lt;/span&gt;
&lt;span class="s"&gt;README.md&lt;/span&gt;
&lt;span class="s"&gt;/*.yaml&lt;/span&gt;
&lt;span class="s"&gt;/*.yml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. &lt;strong&gt;Building Docker Image:&lt;/strong&gt; Building a Docker Image and Running it.&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/oVEOcd41MFU" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;We're all set up, let’s build an image and then run our backend container. Make sure your MySQL container is already running.&lt;/p&gt;

&lt;p&gt;Please start the MySQL container from Docker Desktop. If you have never created a MySQL container before. Please follow this section &lt;a href="https://dev.to/utkarsh4321/part1-how-to-containerize-a-full-stack-app-react-express-and-mysql-4coi-temp-slug-660007?preview=bfb6a372461962e96649ca83880348493f45b5463d0ada02d548821f773dcb82753728742828d46aecb3508290f0237ee88237ba0a195d93ccd55c7d#db"&gt;&lt;strong&gt;Setting up the Database&lt;/strong&gt;&lt;/a&gt; from the &lt;strong&gt;Part-1&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once you set up the MySQL container. Go to the &lt;strong&gt;backend&lt;/strong&gt; directory in the terminal  and run the following command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; my-todo-backend-image:v1 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hope your Docker image got created. You can check inside the Docker Desktop, in the image section.&lt;/p&gt;

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

&lt;p&gt;Now let’s run a new container for our todo backend in the same terminal, run the following command&lt;/p&gt;

&lt;p&gt;Note: Before running any command, please stop your already running dev server for the backend if you previously started it. Otherwise, the port will be occupied. And we are not able to run our container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 5500:5500 &lt;span class="nt"&gt;--env-file&lt;/span&gt; ./.env &lt;span class="nt"&gt;--name&lt;/span&gt; my-todo-backend-container my-todo-backend-image:v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Understanding the Command
&lt;/h3&gt;

&lt;p&gt;When we spin up our backend, we use a single, powerful command. Here is exactly what is happening under the hood:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 5500:5500 &lt;span class="nt"&gt;--env-file&lt;/span&gt; ./.env &lt;span class="nt"&gt;--name&lt;/span&gt; my-todo-backend-container my-todo-backend-image:v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;docker run&lt;/code&gt;&lt;/strong&gt;: The core command that tells Docker to create and start a new container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-it&lt;/code&gt;&lt;/strong&gt;: A combination of &lt;code&gt;-i&lt;/code&gt; (interactive) and &lt;code&gt;-t&lt;/code&gt; (tty). This connects your terminal to the container, which is perfect for watching live logs or debugging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-p 5500:5500&lt;/code&gt;&lt;/strong&gt;: Port mapping (&lt;code&gt;host_port:container_port&lt;/code&gt;). This binds port &lt;code&gt;5500&lt;/code&gt; on your local machine to port &lt;code&gt;5500&lt;/code&gt; inside the isolated container so you can access your API via &lt;code&gt;localhost:5500&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--env-file ./.env&lt;/code&gt;&lt;/strong&gt;: Security best practice! Instead of hardcoding secrets, this loads our environment variables (like database URIs or API keys) directly from our local &lt;code&gt;.env&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--name my-todo-backend-container&lt;/code&gt;&lt;/strong&gt;: Assigns a readable, custom name to the container. If you skip this, Docker assigns a random, quirky name (like &lt;code&gt;sleepy_einstein&lt;/code&gt;), making it harder to find later. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;my-todo-backend-image:v1&lt;/code&gt;&lt;/strong&gt;: The blueprint. This tells Docker exactly which image and version (&lt;code&gt;v1&lt;/code&gt; tag) to use to build the container. &lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pro-tip:&lt;/strong&gt; If you want this container to run quietly in the background without tying up your terminal, swap the &lt;code&gt;-it&lt;/code&gt; flag for &lt;code&gt;-d&lt;/code&gt; (detached mode)!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you run it, your container will crash. Oops, what happened this&lt;/p&gt;

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

&lt;p&gt;If you follow the above instructions, you will be able to see the above error. After running the above command.&lt;/p&gt;

&lt;p&gt;So what we did wrong. Why are we getting this error?🤔&lt;/p&gt;

&lt;p&gt;If you see the error. We are getting this error from &lt;strong&gt;line no 10&lt;/strong&gt; inside the &lt;strong&gt;migrate.js&lt;/strong&gt; file.&lt;/p&gt;

&lt;p&gt;Let’s check our &lt;code&gt;migrate.js&lt;/code&gt; file &lt;/p&gt;

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

&lt;p&gt;In &lt;strong&gt;line no 10&lt;/strong&gt; we are calling &lt;code&gt;mysql.createConnection&lt;/code&gt;. Ok, the issue is with the database connection. &lt;/p&gt;

&lt;p&gt;Let’s check our &lt;code&gt;.env&lt;/code&gt; file &lt;/p&gt;

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

&lt;h3&gt;
  
  
  The &lt;code&gt;localhost&lt;/code&gt; Problem
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Setup:&lt;/strong&gt; In our environment variables, we set &lt;code&gt;DB_HOST&lt;/code&gt; to &lt;strong&gt;localhost&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it worked before:&lt;/strong&gt; This setup works perfectly when running the backend directly on our local machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Container Shift:&lt;/strong&gt; Right now, however, we are running the backend inside an isolated Docker container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Core Issue:&lt;/strong&gt; Inside this specific backend container, there is no MySQL server running on its internal &lt;code&gt;localhost&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Error:&lt;/strong&gt; When the code runs, it searches the container's internal network for a database, comes up empty-handed, and throws an error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Reality:&lt;/strong&gt; Our MySQL server is actually running inside a completely &lt;em&gt;different&lt;/em&gt; container, outside of the backend container's environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Question:&lt;/strong&gt; So, how do we get these two separate containers to talk to each other? What is the solution?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can fix this in two ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Either we pass the &lt;strong&gt;IP address&lt;/strong&gt; of the container on which MySQL is running.&lt;/li&gt;
&lt;li&gt;Or we use &lt;code&gt;host.docker.internal&lt;/code&gt; as a special DNS name. This allows code running inside a container to talk to a service running directly on your computer (the host machine).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's see both ways one by one and then decide which is better&lt;/p&gt;

&lt;h4&gt;
  
  
  1. &lt;strong&gt;IP Address approach&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;So we need to get the IP address of the MySQL container&lt;/p&gt;

&lt;p&gt;Run the following command to get the IP address of the MySQL container&lt;/p&gt;

&lt;p&gt;Note: This approach works regardless of whether our database is exposed or not to our host machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker inspect &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s1"&gt;'{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}'&lt;/span&gt; mysql-container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Breaking it down:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;docker inspect&lt;/code&gt;&lt;/strong&gt;: This command digs into Docker and returns a massive JSON object filled with low-level details about your container.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;-f&lt;/code&gt; (or &lt;code&gt;--format&lt;/code&gt;)&lt;/strong&gt;: Because the default output is hundreds of lines long, we use this flag to filter it. It uses Go templates to extract exactly what we are looking for. &lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;'{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}'&lt;/code&gt;&lt;/strong&gt;: This is the Go template syntax. It tells Docker to look inside the &lt;code&gt;NetworkSettings&lt;/code&gt;, loop through the attached &lt;code&gt;Networks&lt;/code&gt;, and print out just the raw &lt;code&gt;IPAddress&lt;/code&gt; string.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;&amp;lt;container_name_or_id&amp;gt;&lt;/code&gt;&lt;/strong&gt;: Replace this with the actual name or ID of your database container.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you run the above command, you will get the IP of &lt;code&gt;mysql-container&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;Copy the IP from the terminal and change &lt;code&gt;DB_HOST&lt;/code&gt; from &lt;code&gt;localhost&lt;/code&gt; to &lt;code&gt;&amp;lt;IP ADDRESS&amp;gt;&lt;/code&gt; in your &lt;strong&gt;.env&lt;/strong&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB_CONNECTION=mysql
DB_HOST=172.17.0.2
DB_PORT=3306
DB_DATABASE=my_testing_db
DB_USERNAME=root
DB_PASSWORD=myrootpassword
ALLOWED_ORIGIN=http://localhost:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you update your' .env' file, try restarting the backend container with the &lt;code&gt;docker run&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;Please delete the older container or change the name of the container in the command.&lt;/p&gt;

&lt;p&gt;I already deleted the older container, so I'm running the command with the same container name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 5500:5500 &lt;span class="nt"&gt;--env-file&lt;/span&gt; ./.env &lt;span class="nt"&gt;--name&lt;/span&gt; my-todo-backend-container my-todo-backend-image:v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🤞&lt;/p&gt;

&lt;p&gt;Hurray, it's working now. If it successfully started, your terminal now looks like this&lt;/p&gt;

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

&lt;p&gt;Now try out this URL &lt;code&gt;http://localhost:5500/api/todos&lt;/code&gt; in your browser&lt;/p&gt;

&lt;p&gt;You will see seeded todos in the response&lt;/p&gt;

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

&lt;p&gt;Ok, we've seen the IP approach. But the IP address approach is not good because IP is Temporary. If your computer restarts Docker, assign a new IP address when the container restarts. That will crash our backend. So we go with the second approach.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. &lt;strong&gt;Use of &lt;code&gt;host.docker.internal&lt;/code&gt; as a special DNS name&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Note: This approach only works if your container is exposed to the host machine; otherwise, this approach also does not work. We exposed the MySQL container to port 3306 on our host machine, so this approach works in our case.&lt;/p&gt;

&lt;p&gt;First, check if MySQL is running or not. It is exposed to port &lt;strong&gt;3306&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;To check it. Go to Docker Desktop. And in your MySQL container row, check the Port column.&lt;/p&gt;

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

&lt;p&gt;Go to your .env and replace the &lt;code&gt;localhost&lt;/code&gt; value with our special DNS name.&lt;/p&gt;

&lt;p&gt;Replace &lt;code&gt;172.17.0.2&lt;/code&gt; with &lt;code&gt;host.docker.internal&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB_CONNECTION=mysql
DB_HOST=host.docker.internal
DB_PORT=3306
DB_DATABASE=my_testing_db
DB_USERNAME=root
DB_PASSWORD=myrootpassword
ALLOWED_ORIGIN=http://localhost:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, before running our container again. Please delete the older backend container that we created; otherwise, Docker will throw an error. Or you can change the name of the Docker container in the command. I'm using the same container name.&lt;/p&gt;

&lt;p&gt;Run Docker container via the following command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 5500:5500 &lt;span class="nt"&gt;--env-file&lt;/span&gt; ./.env &lt;span class="nt"&gt;--name&lt;/span&gt; my-todo-backend-container my-todo-backend-image:v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open this link in your browser: &lt;code&gt;http://localhost:5500/api/todos&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You will see the same result as we got above. You will see dummy seeded todos. &lt;br&gt;
Hurray, we're done with this approach as well.&lt;/p&gt;

&lt;p&gt;But which approach do we choose for our production?&lt;/p&gt;

&lt;p&gt;Because the first approach we discussed is not reliable. If the IP address of the container changes, our backend breaks. &lt;/p&gt;

&lt;p&gt;Or if we go with the second approach, then we need to expose our database, then only. We can connect with the database. But in production, we will not expose our DB. It's run inside the container without exposing to the outer world.&lt;/p&gt;

&lt;p&gt;Then what should we do now? 🤔&lt;/p&gt;

&lt;p&gt;We will talk about it later. But before that, we need to resolve a very important problem that exists with our current setup. &lt;/p&gt;

&lt;p&gt;That is a &lt;strong&gt;Data persistence&lt;/strong&gt; issue. We will discuss it in the next section.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. &lt;strong&gt;Persisting MySQL Data in Docker Containers:&lt;/strong&gt; Secure your MySQL data against container destruction.&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/cufk56Jm-_Q" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;By default, Docker containers are ephemeral or temporary.&lt;br&gt;
If your MySQL container stops, updates, or gets removed, all the data inside it vanishes.&lt;/p&gt;

&lt;p&gt;To make your database robust and production-ready, you must implement a reliable data persistence strategy.&lt;/p&gt;

&lt;p&gt;We saw the theory above; now let's try it out.&lt;/p&gt;

&lt;p&gt;If your frontend container is running, go to &lt;code&gt;http://localhost:8080/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you are not running a frontend container, you can also use &lt;strong&gt;Postman&lt;/strong&gt; to create some dummy todos. By using the URL &lt;code&gt;http://localhost:5500/api/todos&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I created two todos&lt;/p&gt;

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

&lt;p&gt;Now we delete our MySQL container. &lt;/p&gt;

&lt;p&gt;First, we need to stop the container and then delete it&lt;/p&gt;

&lt;p&gt;To stop the container run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker stop mysql-container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Note: Once the MySQL container stops. Your backend container also crashed and stopped due to a DB connectivity issue.&lt;/p&gt;

&lt;p&gt;Now to delete the container run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;rm &lt;/span&gt;mysql-container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Now we again create a new MySQL container. &lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--name&lt;/span&gt; mysql-container &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;myrootpassword &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my_testing_db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3306:3306 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; mysql:8.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the MySQL container has started. Go to Docker Desktop and start your backend container as well.&lt;/p&gt;

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

&lt;p&gt;Once the backend container starts. Go to your frontend in the browser, go to the URL &lt;code&gt;http://localhost:8080/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You will not be able to see the todos we created previously. Only dummy-seeded todos present that are generated on the startup of the backend container.&lt;/p&gt;

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

&lt;p&gt;See our data lost due to the removal of our container. So how do we persist our data?&lt;/p&gt;

&lt;p&gt;How can we make our MySQL data persistent?&lt;/p&gt;

&lt;p&gt;Here Docker &lt;strong&gt;Named Volumes&lt;/strong&gt; comes to rescue us to avoid this condition.&lt;/p&gt;

&lt;p&gt;Think of a &lt;strong&gt;Named Volume&lt;/strong&gt; like an external USB hard drive for your container.&lt;/p&gt;

&lt;p&gt;Normally, a container is like a temporary sandbox—if you delete the container, everything inside the sandbox is permanently destroyed.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Named Volume&lt;/strong&gt; solves this by permitting Docker to create a special, safe folder on your actual computer. You "plug" this volume into your container. Now, whenever your database saves information, it gets written directly to that safe folder instead of the temporary sandbox.&lt;/p&gt;

&lt;p&gt;If the container crashes or gets deleted, your data stays completely safe on your computer, ready to be plugged right back into a brand new container.&lt;/p&gt;

&lt;p&gt;Now let's see this in action&lt;/p&gt;

&lt;p&gt;First, things first, stop the MySQL container and delete it. Similarly to what we did above, or you can do it from Docker Desktop. I'm using Docker Desktop to delete it.&lt;/p&gt;

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

&lt;p&gt;Once the MySQL container is deleted. Firstly, we need to create volume. &lt;/p&gt;

&lt;p&gt;To create a volume run command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker volume create mysql_db_data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Command Breakdown
&lt;/h3&gt;

&lt;p&gt;This command tells Docker to set up a brand new, empty storage space on your host machine. &lt;/p&gt;

&lt;p&gt;Here is the exact breakdown of what each word does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;docker&lt;/code&gt;&lt;/strong&gt;: Calls the main Docker program.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;volume&lt;/code&gt;&lt;/strong&gt;: Tells Docker you want to interact with its storage management system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;create&lt;/code&gt;&lt;/strong&gt;: The specific action you want to take (make a new one).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;mysql_db_data&lt;/code&gt;&lt;/strong&gt;: The custom name you are giving this specific volume so you can easily find it and attach it to containers later.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; This is like taking a brand new, blank USB flash drive and putting a sticker on it that says &lt;code&gt;mysql_db_data&lt;/code&gt; so you know exactly what it's for.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now run your MySQL container with the &lt;strong&gt;mount&lt;/strong&gt; flag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--name&lt;/span&gt; mysql-container &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;myrootpassword &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my_testing_db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3306:3306 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysql_db_data,target&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/mysql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; mysql:8.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Volume Attachment Breakdown
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--mount&lt;/code&gt;&lt;/strong&gt;: The modern, more readable flag for attaching volumes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;source=mysql_db_data&lt;/code&gt;&lt;/strong&gt;: Tells Docker to use the named volume you created previously.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;target=/var/lib/mysql&lt;/code&gt;&lt;/strong&gt;: The directory inside the MySQL container where the database actually writes its data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once your MySQL container has started. Now start your backend container as well.&lt;/p&gt;

&lt;p&gt;Hope your frontend container is running. Go to your frontend at &lt;code&gt;http://localhost:8080/&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;And create some dummy todos again. I created two new todos. Which comes after our seeded todos?&lt;/p&gt;

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

&lt;p&gt;So, to check whether the data persists or not. We need to remove the container. And again run the container with &lt;strong&gt;--mount&lt;/strong&gt; flag.&lt;/p&gt;

&lt;p&gt;I'm first stopping the MySQL container and then deleting it.&lt;br&gt;
Running both commands together&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker stop mysql-container
docker &lt;span class="nb"&gt;rm &lt;/span&gt;mysql-container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Now again run the same command to run the MySQL container with the &lt;strong&gt;--mount&lt;/strong&gt; flag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--name&lt;/span&gt; mysql-container &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;myrootpassword &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my_testing_db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3306:3306 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysql_db_data,target&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/mysql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; mysql:8.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbky7q0uw277yd248gexx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbky7q0uw277yd248gexx.png" alt="docker command to run the mysql container with volume" width="798" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the MySQL container has started. Start your backend container as well. Once your backend container has started.&lt;/p&gt;

&lt;p&gt;Now go to your frontend. You will be able to see the todos we created previously are present.&lt;/p&gt;

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

&lt;p&gt;We resolved the data persistence problem for our project.&lt;/p&gt;

&lt;p&gt;Now we move back to our older problem. &lt;br&gt;
For production, how can we connect our backend reliably with our MySQL database without exposing it outside?&lt;/p&gt;

&lt;p&gt;Note: In production, we use &lt;strong&gt;Kubernetes&lt;/strong&gt; to manage our containers.&lt;/p&gt;

&lt;p&gt;But we are trying to simulate the same behaviour.&lt;/p&gt;

&lt;p&gt;To solve our problem, we need to set up &lt;strong&gt;Custom Docker Networks&lt;/strong&gt;. So that our backend can talk securely with the MySQL database.&lt;/p&gt;
&lt;h3&gt;
  
  
  7. &lt;strong&gt;Isolating Services with Docker Networks:&lt;/strong&gt; Use custom internal networks to securely connect your backend to MySQL.&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/1HJvR-Rn2RQ" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;By default, Docker containers cannot talk to each other easily. &lt;/li&gt;
&lt;li&gt;However, when you place them on the same &lt;strong&gt;custom network&lt;/strong&gt;, Docker provides an automatic &lt;strong&gt;internal DNS&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;This allows your backend to talk to your database using the &lt;strong&gt;container's name&lt;/strong&gt;, completely securely, without ever publishing ports to your host machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Step 1: Create a Custom Network
&lt;/h4&gt;

&lt;p&gt;First, create an isolated internal network for your application services to share.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker network create my-todo-network
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h4&gt;
  
  
  Step 2: Start the MySQL Container (Securely) inside a custom network
&lt;/h4&gt;

&lt;p&gt;Spin up your MySQL container. Notice that we omit the -p 3306:3306 flag here. We also attach it to the network using --network flag that we just created.&lt;/p&gt;

&lt;p&gt;Note: I'm also changing container name of our mysql db container. Also please stop the mysql-container. Other wise this container will not start. We are trying to use same volume that's used by another container. We can use it only after stopping it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; mysql-secure-container &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; my-todo-network &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;myrootpassword &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my_testing_db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysql_db_data,target&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/mysql &lt;span class="se"&gt;\&lt;/span&gt;
  mysql:8.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Because there is no -p flag, nothing on your host machine (or the internet) can access this database. It is entirely locked inside Docker's internal environment.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3: Start Your Backend Container
&lt;/h4&gt;

&lt;p&gt;We start our backend inside the same network where our Mysql Db present.&lt;/p&gt;

&lt;p&gt;To securely connect with the MySQL database. We can use the &lt;strong&gt;database container's name&lt;/strong&gt; as the host. So instead of using &lt;strong&gt;localhost&lt;/strong&gt; or an IP address. We will use the database container's name that is &lt;strong&gt;mysql-secure-container&lt;/strong&gt; as the &lt;strong&gt;host&lt;/strong&gt; inside the &lt;strong&gt;.env&lt;/strong&gt; file.&lt;/p&gt;

&lt;p&gt;Go to the &lt;strong&gt;.env&lt;/strong&gt; file and change the &lt;strong&gt;DB_HOST&lt;/strong&gt; value from &lt;code&gt;host.docker.internal&lt;/code&gt; to &lt;code&gt;mysql-secure-container&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB_CONNECTION=mysql
DB_HOST=mysql-secure-container
DB_PORT=3306
DB_DATABASE=my_testing_db
DB_USERNAME=root
DB_PASSWORD=myrootpassword
ALLOWED_ORIGIN=http://localhost:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now start the backend container. But before starting it. Please delete the older backend container from Docker Desktop.&lt;/p&gt;

&lt;p&gt;Once you have deleted the older backend container, run the following command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; my-todo-backend-container &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; my-todo-network &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env-file&lt;/span&gt; ./.env &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 5500:5500 &lt;span class="se"&gt;\&lt;/span&gt;
  my-todo-backend-image:v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Once your backend is up and running, check the URL &lt;code&gt;http://localhost:5500/api/todos&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You will see previously created todos.&lt;/p&gt;

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

&lt;p&gt;Also, go to your frontend and check whether it's working or not.&lt;/p&gt;

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

&lt;p&gt;Hurray, we're done. We securely connected with the MySQL Database without exposing it. If you follow this blog till here, pat on the back.&lt;/p&gt;

&lt;p&gt;Now what's next?&lt;/p&gt;

&lt;p&gt;Let's recap what we've accomplished so far using the Docker CLI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Built and Deployed:&lt;/strong&gt; We successfully built our application images and manually ran both our backend and MySQL database containers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secured Data:&lt;/strong&gt; We configured Docker Named Volumes to guarantee our database records persist, even if the container is destroyed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolated Communication:&lt;/strong&gt; We established a custom Docker network to ensure our services (frontend, backend, and database) can talk to each other securely without exposing sensitive ports to the host machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While this manual CLI approach gives you a great understanding of how Docker works under the hood, it introduces a massive margin for human error. If a single flag is missed, a port is mapped incorrectly, or a network name is misspelled in your terminal, the entire system breaks. &lt;/p&gt;

&lt;p&gt;Right now, we are only managing three moving parts: a frontend, a backend, and a database. But imagine if you were working with a microservices architecture that had ten, twenty, or fifty distinct services. How could you possibly start, link, and manage them all by typing out &lt;code&gt;docker build&lt;/code&gt; and &lt;code&gt;docker run&lt;/code&gt; commands manually for every single service? &lt;/p&gt;

&lt;p&gt;The short answer is: you wouldn't. In the next part of this series, we will look at how to solve this exact problem and automate our entire architecture using Docker Compose.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. &lt;strong&gt;Automating with Docker Compose:&lt;/strong&gt; Spin up your complete architecture, including networks and volumes, with just one command.&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/OK2T6_pYqZ0" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h4&gt;
  
  
  Why Do We Need Docker Compose? (The Problem)
&lt;/h4&gt;

&lt;p&gt;Imagine your full-stack web application relies on three distinct services to function:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; React&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Node.js (Express)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; MySQL&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Without Docker Compose, you are forced to type out long, error-prone commands in your terminal every single time you start your development environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Create the isolated network&lt;/span&gt;
docker network create my-net

&lt;span class="c"&gt;# 2. Run the database container with volume mounts&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 5500:5500 &lt;span class="nt"&gt;--network&lt;/span&gt; my-net &lt;span class="nt"&gt;--name&lt;/span&gt; db mysql:8.0

&lt;span class="c"&gt;# 3. Run the backend API container&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--network&lt;/span&gt; my-net &lt;span class="nt"&gt;--name&lt;/span&gt; backend my-node-app

&lt;span class="c"&gt;# 4. Run the frontend container and map the public port&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:80 &lt;span class="nt"&gt;--network&lt;/span&gt; my-net &lt;span class="nt"&gt;--name&lt;/span&gt; frontend my-react-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you forget a single environment flag, misconfigure a port, or mistype a network name, your services fail to communicate and your entire application breaks. Managing this manually becomes a nightmare as your architecture grows.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Solution: What is Docker Compose?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Docker Compose&lt;/strong&gt; is a tool that allows you to define and run multi-container Docker applications. Instead of memorizing and executing four different &lt;code&gt;docker run&lt;/code&gt; commands every time you want to code, you define your entire infrastructure once inside a single blueprint file: &lt;code&gt;docker-compose.yml&lt;/code&gt; (or &lt;code&gt;compose.yaml&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;By defining your architecture as code, you can launch your entire ecosystem—frontend, backend, and database—with just one simple command instead of juggling multiple complex terminal scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Key Advantage: Automatic Networking 🌐
&lt;/h4&gt;

&lt;p&gt;One of the best features of Docker Compose is that it handles all the networking for you automatically. &lt;/p&gt;

&lt;p&gt;The moment you start your compose file, it instantly creates a private, secure network just for your application services. &lt;/p&gt;

&lt;p&gt;Here is why that makes your life easier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No More IP Addresses:&lt;/strong&gt; You don't have to keep track of changing container IP addresses anymore.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple Hostnames:&lt;/strong&gt; Your backend service can talk to your database securely using just the database's &lt;strong&gt;service name&lt;/strong&gt; (the name you give it in your YAML file) as the hostname. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docker completely handles all the complicated internal routing dynamically behind the scenes.&lt;/p&gt;

&lt;h4&gt;
  
  
  Let’s Write Our Docker Compose File
&lt;/h4&gt;

&lt;p&gt;Navigate to your backend folder and create a file named &lt;code&gt;compose.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And then paste below code inside it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Your Database Service&lt;/span&gt;
  &lt;span class="na"&gt;my-todo-database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql:8.0.44&lt;/span&gt; &lt;span class="c1"&gt;# or mysql&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DB_DATABASE}&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DB_PASSWORD}&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mysqladmin"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-h"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;20s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mysql_db_data:/var/lib/mysql&lt;/span&gt;

  &lt;span class="c1"&gt;# Your App Service&lt;/span&gt;
  &lt;span class="na"&gt;todo-backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;on-failure&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5500:5500"&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;my-todo-database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mysql_db_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Docker Compose Configuration Explained
&lt;/h4&gt;

&lt;p&gt;This &lt;code&gt;docker-compose.yml&lt;/code&gt; file sets up a robust, two-tier architecture consisting of a database and a backend application. Here is the line-by-line breakdown of what this configuration tells Docker to do.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Services Block
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;services:&lt;/code&gt;&lt;br&gt;
The root level of the Compose file. It defines all the different containers (services) that make up your application.&lt;/p&gt;
&lt;h5&gt;
  
  
  The &lt;code&gt;my-todo-database&lt;/code&gt; Service
&lt;/h5&gt;

&lt;p&gt;&lt;code&gt;my-todo-database:&lt;/code&gt;&lt;br&gt;
Names the first service. This name acts as the hostname on the internal Docker network (so the backend can connect to it using &lt;code&gt;my-todo-database&lt;/code&gt; as the host).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;image: mysql:8.0.44&lt;/code&gt;&lt;br&gt;
Pulls the official MySQL image from Docker Hub, specifically version 8.0.44, instead of building it from a local Dockerfile.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;restart: always&lt;/code&gt;&lt;br&gt;
Tells Docker always to restart this container if it stops, crashes, or if the server itself reboots.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;environment:&lt;/code&gt;&lt;br&gt;
Begins the block for setting environment variables inside the MySQL container. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;MYSQL_DATABASE: ${DB_DATABASE}&lt;/code&gt;&lt;br&gt;
Creates a default database on startup. The value is dynamically injected from a variable named &lt;code&gt;DB_DATABASE&lt;/code&gt; (usually found in an adjacent &lt;code&gt;.env&lt;/code&gt; file).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}&lt;/code&gt;&lt;br&gt;
Sets the MySQL &lt;code&gt;root&lt;/code&gt; user password, also securely injected from the &lt;code&gt;DB_PASSWORD&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;healthcheck:&lt;/code&gt;&lt;br&gt;
Configures a test to determine if the container is actually ready to handle requests, not just running.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]&lt;/code&gt;&lt;br&gt;
The command Docker runs inside the container to check its health. Here, it pings the MySQL server to see if it is actively accepting connections.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;timeout: 20s&lt;/code&gt;&lt;br&gt;
If the &lt;code&gt;mysqladmin ping&lt;/code&gt; command takes longer than 20 seconds to return a result, Docker considers that specific check a failure.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;retries: 10&lt;/code&gt;&lt;br&gt;
Docker will try the health check 10 times. If it fails 10 times consecutively, the container is officially marked as "unhealthy".&lt;/p&gt;

&lt;p&gt;&lt;code&gt;volumes:&lt;/code&gt;&lt;br&gt;
Begins the block for mounting storage to the container.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;- mysql_db_data:/var/lib/mysql&lt;/code&gt;&lt;br&gt;
Maps a named Docker volume (&lt;code&gt;mysql_db_data&lt;/code&gt;) to the path inside the container where MySQL stores its raw data (&lt;code&gt;/var/lib/mysql&lt;/code&gt;). This ensures your database records survive even if the container is destroyed or recreated.&lt;/p&gt;


&lt;h5&gt;
  
  
  The &lt;code&gt;todo-backend&lt;/code&gt; Service
&lt;/h5&gt;

&lt;p&gt;&lt;code&gt;todo-backend:&lt;/code&gt;&lt;br&gt;
Name your application service.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;build: .&lt;/code&gt;&lt;br&gt;
Instead of pulling a pre-made image, this tells Docker to build a new image using the &lt;code&gt;Dockerfile&lt;/code&gt; located in the current directory (&lt;code&gt;.&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;restart: on-failure&lt;/code&gt;&lt;br&gt;
Unlike the database, this container will only restart if it exits with a non-zero exit code (indicating a crash or error). It won't restart if you intentionally stop it gracefully.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ports:&lt;/code&gt;&lt;br&gt;
Begins the block for mapping network ports.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;- "5500:5500"&lt;/code&gt;&lt;br&gt;
Maps port &lt;code&gt;5500&lt;/code&gt; on your host machine (your laptop or server) to port &lt;code&gt;5500&lt;/code&gt; inside the container. This allows external traffic (like your browser or a frontend app) to reach your API.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;env_file:&lt;/code&gt;&lt;br&gt;
Begins the block for loading bulk environment variables.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;- .env&lt;/code&gt;&lt;br&gt;
Tells Compose to load all key-value pairs from a local &lt;code&gt;.env&lt;/code&gt; file and inject them into the backend container.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;depends_on:&lt;/code&gt;&lt;br&gt;
Controls the startup order of your services.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;my-todo-database:&lt;/code&gt;&lt;br&gt;
Specifies that the &lt;code&gt;backend&lt;/code&gt; relies on the &lt;code&gt;my-todo-database&lt;/code&gt; service.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;condition: service_healthy&lt;/code&gt;&lt;br&gt;
This is the most crucial part of the file. It tells Docker Compose &lt;strong&gt;not&lt;/strong&gt; to start the &lt;code&gt;backend&lt;/code&gt; container until &lt;code&gt;my-todo-database&lt;/code&gt; has successfully passed its &lt;code&gt;healthcheck&lt;/code&gt;. This prevents your backend from crashing on startup due to trying to connect to a database that is still booting up.&lt;/p&gt;


&lt;h4&gt;
  
  
  The Volumes Block
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;volumes:&lt;/code&gt;&lt;br&gt;
The top-level configuration for volumes used by the services above.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mysql_db_data:&lt;/code&gt;&lt;br&gt;
Declares the volume name referenced by the database service.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;external: true&lt;/code&gt;&lt;br&gt;
Tells Docker Compose that this volume was created manually outside of this Compose file (e.g., a dev ran &lt;code&gt;docker volume create mysql_db_data&lt;/code&gt; in the terminal). Also, compose, not try to create a new volume. It will use this volume.&lt;/p&gt;

&lt;p&gt;Please stop all the previously running containers. Otherwise, port conflicts will occur except for the frontend container.&lt;/p&gt;
&lt;h4&gt;
  
  
  Running our Docker Compose
&lt;/h4&gt;

&lt;p&gt;First, go to your backend &lt;code&gt;.env&lt;/code&gt; file. And change the value of &lt;code&gt;DB_HOST&lt;/code&gt; from &lt;code&gt;mysql-secure-container&lt;/code&gt; to &lt;code&gt;my-todo-database&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB_CONNECTION=mysql
DB_HOST=my-todo-database &amp;lt;-- change this
DB_PORT=3306
DB_DATABASE=my_testing_db
DB_USERNAME=root
DB_PASSWORD=myrootpassword
ALLOWED_ORIGIN=http://localhost:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in the terminal, go to the backend directory and run the Docker Compose command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;&lt;code&gt;docker compose up&lt;/code&gt;&lt;/strong&gt; reads your &lt;code&gt;compose.yml&lt;/code&gt; file to automatically build, network, and start all your application's containers in the correct order.&lt;/p&gt;

&lt;p&gt;Once you run this command, immediately go to Docker Desktop, and you will see MySQL started, but your backend app has not started yet.&lt;/p&gt;

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

&lt;p&gt;But after some time, it automatically gets started. If no error occurs.&lt;/p&gt;

&lt;p&gt;After some time, when you see something like below in your CLI, your backend has started successfully &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F21j6ltpvpmyoi8xrq3c5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F21j6ltpvpmyoi8xrq3c5.png" alt="docker compose up cli output after on backend start" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you see seeding data skipped. Why, because we are using our older volume that we created previously.&lt;/p&gt;

&lt;p&gt;Once both the DB and backend container start, go to &lt;code&gt;http://localhost:5500/api/todos&lt;/code&gt;, you will be able to see our previously created todos also present.&lt;/p&gt;

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

&lt;p&gt;Also, check your frontend as well. By navigating to &lt;code&gt;http://localhost:5500/api/todos&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;If you can see our previously created todos. We had done with this setup successfully. Hope you enjoyed it. You can learn more about &lt;strong&gt;Docker Compose&lt;/strong&gt;. Now this blog post is over. If you are interested to setup frontend as well with Docker Compose, follow along with the blog.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. &lt;strong&gt;Bonus Section:&lt;/strong&gt; Add a React frontend to complete your stack.&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Prefer Video? Watch Here&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/8i1H9JimkVc" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Go to &lt;code&gt;compose.yml&lt;/code&gt; that we created previously. Here inside the service section. We just need to add our &lt;strong&gt;frontend&lt;/strong&gt; service as well that's it. &lt;/p&gt;

&lt;p&gt;after &lt;code&gt;todo-backend&lt;/code&gt;service paste following code snippet&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt; &lt;span class="na"&gt;todo-frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../frontend&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;../frontend/.env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Frontend Service
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;todo-frontend:&lt;/code&gt;&lt;br&gt;
Names your frontend application service. This creates a dedicated container for your UI (like React or Vue) on the same Docker network.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;build: ../frontend&lt;/code&gt;&lt;br&gt;
Tells Docker to build the container image using the &lt;code&gt;Dockerfile&lt;/code&gt; located in the &lt;code&gt;frontend&lt;/code&gt; directory. This keeps your frontend build logic cleanly separated from your backend.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ports:&lt;/code&gt;&lt;br&gt;
Begins the port mapping configuration.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;- "8080:8080"&lt;/code&gt;&lt;br&gt;
Maps port &lt;code&gt;8080&lt;/code&gt; on your local machine to port &lt;code&gt;8080&lt;/code&gt; inside the container. You will view your app by visiting &lt;code&gt;http://localhost:8080&lt;/code&gt; in your browser, while the internal web server (like Nginx) listens on the HTTP port 8080.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;env_file:&lt;/code&gt;&lt;br&gt;
Begins the block for injecting environment variables.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;- ../frontend/.env&lt;/code&gt;&lt;br&gt;
Loads UI-specific environment variables (like your backend API URL) from a dedicated &lt;code&gt;.env&lt;/code&gt; file inside the &lt;code&gt;frontend&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;That's it now save the &lt;strong&gt;compose.yml&lt;/strong&gt; file and run the &lt;strong&gt;docker compose&lt;/strong&gt; command inside the &lt;strong&gt;backend&lt;/strong&gt; directory.&lt;/p&gt;

&lt;p&gt;After adding &lt;strong&gt;frontend&lt;/strong&gt; service in your &lt;code&gt;compose.yml&lt;/code&gt;. It's finally looks like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Your Database Service&lt;/span&gt;
  &lt;span class="na"&gt;my-todo-database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql:8.0.44&lt;/span&gt; &lt;span class="c1"&gt;# or mysql&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DB_DATABASE}&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DB_PASSWORD}&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mysqladmin"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-h"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;20s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mysql_db_data:/var/lib/mysql&lt;/span&gt;

  &lt;span class="c1"&gt;# Your App Service&lt;/span&gt;
  &lt;span class="na"&gt;todo-backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;on-failure&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5500:5500"&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;my-todo-database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
  &lt;span class="na"&gt;todo-frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../frontend&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;../frontend/.env&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mysql_db_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: Hope you stopped all the previously running container.&lt;/p&gt;

&lt;p&gt;Run docker compose command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above command run by default in interactive mode. Which hijack your terminal. If you want to run your container in detach mode. you can use &lt;code&gt;-d&lt;/code&gt; detached flag.&lt;/p&gt;

&lt;p&gt;After running above command you will see all three services spin up. You can check in &lt;strong&gt;docker desktop&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Now also check your frontend by visiting &lt;code&gt;http://localhost:8080/&lt;/code&gt;.Hope you're able to see our todos.&lt;/p&gt;

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

&lt;p&gt;Our frontend is working fine. As expected. &lt;/p&gt;

&lt;h3&gt;
  
  
  10. &lt;strong&gt;Wrap Up:&lt;/strong&gt;&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;We're done with our full-stack todo app deployment using Docker. If you follow along till here, give yourself a gift. Because you really did great work.&lt;/p&gt;

&lt;p&gt;From here, you can learn more about Docker Compose. Or you can start learning about Kubernetes. And use this image to run inside the pod in Kubernetes. There is much more to explore.&lt;/p&gt;

&lt;p&gt;If this helps you in any way. Please like and share your valuable comment  with some love. Hope you like it👋.&lt;/p&gt;

</description>
      <category>fullstack</category>
      <category>backend</category>
      <category>frontend</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Containerize a Full-Stack App (Part 1)-Setting Up the Local Development Environment.</title>
      <dc:creator>utkarsh srivastava</dc:creator>
      <pubDate>Sat, 31 Oct 2026 01:29:40 +0000</pubDate>
      <link>https://dev.to/utkarsh4321/how-to-containerize-a-full-stack-app-part-1-setting-up-the-local-development-environment-18ee</link>
      <guid>https://dev.to/utkarsh4321/how-to-containerize-a-full-stack-app-part-1-setting-up-the-local-development-environment-18ee</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Series Navigation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📍 &lt;strong&gt;Part 1:&lt;/strong&gt; &lt;strong&gt;Setting up locally&lt;/strong&gt; &lt;em&gt;(You are here)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;➡️ &lt;strong&gt;Part 2:&lt;/strong&gt; &lt;a href="https://dev.to/utkarsh4321/how-to-containerize-a-full-stack-app-part-2-optimizing-the-frontend-with-multi-stage-docker-1cbo"&gt;&lt;strong&gt;Frontend Multi-Stage Docker Builds&lt;/strong&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;➡️ &lt;strong&gt;Part 3:&lt;/strong&gt; &lt;a href="http://dev.to/utkarsh4321/how-to-containerize-a-full-stack-app-part-3-containerizing-the-backend-and-mysql-database-3kc8"&gt;&lt;strong&gt;Containerizing the Backend &amp;amp; MySQL&lt;/strong&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Prefer Video? Watch Here
&lt;/h4&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/WjzWC58ZILI" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;In modern web development, Docker has become the standard for ensuring your application runs consistently across dev, test, and production environments. &lt;strong&gt;In this post, we are going to focus specifically on how to containerize a full-stack application built with React, Express.js, and MySQL.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this tutorial, I will guide you through the end-to-end process of containerizing this stack. This is &lt;strong&gt;Part 1&lt;/strong&gt;, so here we will just set up our full-stack app locally. In the upcoming blog post, I will explain the remaining details.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here is what we will cover:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prerequisites:&lt;/strong&gt; Who is this for? (Frontend, Backend, and DevOps developers).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setup:&lt;/strong&gt; Installing Docker Desktop on our machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setting up the Database:&lt;/strong&gt; Setting up the Mysql Database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Baseline:&lt;/strong&gt; Running our full-stack app locally before containerizing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conclusion:&lt;/strong&gt; Summary and final thoughts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s get started.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before we dive in, let’s make sure this guide is right for you. This tutorial is designed to be flexible: whether you want to master the entire full-stack containerization process or need a solution for a specific part, you can leverage the sections that matter to you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who is this for?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Frontend Developers:&lt;/strong&gt; You will learn how to serve your React app efficiently without relying on &lt;code&gt;npm start&lt;/code&gt; in production.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Backend Developers:&lt;/strong&gt; You will learn how to &lt;strong&gt;containerize your backend for production&lt;/strong&gt;, ensuring your API and database run reliably together on any server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DevOps Enthusiasts:&lt;/strong&gt; You will gain hands-on experience packaging web applications and using Docker Compose to run them locally—the essential first step before managing them in a cloud environment.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;⚠️ Important Note:&lt;/strong&gt;&lt;br&gt;
This guide is &lt;strong&gt;not&lt;/strong&gt; for absolute beginners. We assume you already have a working knowledge of &lt;strong&gt;React&lt;/strong&gt; and &lt;strong&gt;Node.js&lt;/strong&gt;. I'm not covering how to write the application code itself, but rather how to package and ship it effectively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you need:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A basic understanding of React and Express.js.&lt;/li&gt;
&lt;li&gt;Familiarity with the command line.&lt;/li&gt;
&lt;li&gt;A code editor (VS Code recommended).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2. &lt;a&gt;&lt;/a&gt;Setup: Installing Docker Desktop on our machine
&lt;/h3&gt;

&lt;p&gt;To work with containers, we need the Docker engine running on our computer. The easiest and most standard way to do this is via &lt;strong&gt;Docker Desktop&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Download:&lt;/strong&gt; Go to the official Docker website &lt;a href="https://docs.docker.com/desktop/setup/install/mac-install/" rel="noopener noreferrer"&gt;for Mac&lt;/a&gt;, &lt;a href="https://docs.docker.com/desktop/setup/install/windows-install/" rel="noopener noreferrer"&gt;for Windows&lt;/a&gt;, &lt;a href="https://docs.docker.com/desktop/setup/install/linux/" rel="noopener noreferrer"&gt;for Linux&lt;/a&gt; and download the installer for your OS (Windows, Mac, or Linux).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install:&lt;/strong&gt; Run the installer and follow the on-screen instructions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start the App:&lt;/strong&gt; Once installed, open the "Docker Desktop" application. &lt;strong&gt;(Important: Docker commands won't work unless this app is running in the background!)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify:&lt;/strong&gt; Open your terminal (or command prompt) and run the following command to make sure everything is connected:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# Output Example: Docker version 29.0.1, build eedd969&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you see a version number, you are ready to go!&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  3.Setting up the Database
&lt;/h3&gt;

&lt;p&gt;To run our backend, we need a MySQL database. Instead of installing the heavy MySQL server software directly on your OS, we will use Docker to spin up a MySQL container.&lt;/p&gt;

&lt;p&gt;Run this command in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--name&lt;/span&gt; mysql-container &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;myrootpassword &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my_testing_db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3306:3306 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; mysql:8.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, go to Docker Desktop and verify that your MySQL container is running successfully&lt;/p&gt;

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

&lt;p&gt;⚠️ Note: &lt;br&gt;
Please save this &lt;strong&gt;password&lt;/strong&gt; and &lt;strong&gt;db name&lt;/strong&gt; as well because we need them later.&lt;br&gt;
Also, if the above command failed. Then try with a different &lt;strong&gt;port number&lt;/strong&gt;. For example, &lt;code&gt;3307&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;docker run --name mysql-container \&lt;br&gt;
  -e MYSQL_ROOT_PASSWORD=myrootpassword \&lt;br&gt;
  -e MYSQL_DATABASE=my_testing_db \&lt;br&gt;
  -p 3306:3306 \&lt;br&gt;
  -d mysql:8.0&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What Happens When You Run This Command&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Docker downloads the &lt;strong&gt;mysql:8.0&lt;/strong&gt; image (if not present).&lt;/li&gt;
&lt;li&gt;Creates a new container named &lt;strong&gt;mysql-container&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Starts the MySQL server inside it.&lt;/li&gt;
&lt;li&gt;Initialises the database &lt;code&gt;my_testing_db&lt;/code&gt; (first run only).&lt;/li&gt;
&lt;li&gt;Sets the root user password to whatever you provided.&lt;/li&gt;
&lt;li&gt;Exposes MySQL on your machine's port &lt;strong&gt;3306&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;docker run&lt;/code&gt;&lt;/strong&gt;: Tells Docker to create and start a new container from a specified image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--name mysql-container&lt;/code&gt;&lt;/strong&gt;: Gives the container a custom name: &lt;strong&gt;mysql-container&lt;/strong&gt;. So we can find it easily. If omitted, Docker assigns a random name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-e MYSQL_ROOT_PASSWORD=myrootpassword&lt;/code&gt;&lt;/strong&gt;: Sets the root password (we'll need this for our backend). The official MySQL image requires the MYSQL_ROOT_PASSWORD environment variable to be set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-e MYSQL_DATABASE=my_testing_db&lt;/code&gt;&lt;/strong&gt;: This will set the database with name &lt;strong&gt;my_testing_db&lt;/strong&gt;. This env variable is optional. It creates a database on container creation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-p 3306:3306&lt;/code&gt;&lt;/strong&gt;: Exposes the database port so our local Node.js app can connect to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-d mysql:8.0&lt;/code&gt;&lt;/strong&gt;: Image name.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  4.&lt;a&gt;&lt;/a&gt; The Baseline:  Running our full-stack app locally before containerizing
&lt;/h3&gt;

&lt;p&gt;Before we start writing Dockerfiles, we need to understand what we are actually containerizing. In this section, we will get the application running on our machine "the old-fashioned way" (mostly) to ensure the code works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this section, we will:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fork and Clone&lt;/strong&gt; the starter repository.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set up the Database&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the Application&lt;/strong&gt; to confirm that everything talks to each other.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  4.1 Fork and Clone the Repository
&lt;/h4&gt;

&lt;p&gt;I have prepared a starter repository that contains a basic React frontend and an Express backend.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fork&lt;/strong&gt; the repository to your own GitHub account.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Go to repo: &lt;a href="https://github.com/utkarsh4321/devops-todo-starter-app" rel="noopener noreferrer"&gt;Repo Link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on Fork&lt;/p&gt;

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

&lt;p&gt;Once forked, clone it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clone&lt;/strong&gt; it to your local machine:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone the repository&lt;/span&gt;
git clone https://github.com/YOUR_GITHUB_USERNAME/devops-todo-starter-app.git

&lt;span class="c"&gt;# Move into the project directory&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;devops-todo-starter-app

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

&lt;/div&gt;


&lt;p&gt;Once you cloned it, open it in &lt;strong&gt;VS Code&lt;/strong&gt; or any IDE.&lt;/p&gt;

&lt;p&gt;Note: There are three branches present for each blog post. Make sure you are in the &lt;strong&gt;main&lt;/strong&gt; branch.&lt;/p&gt;

&lt;p&gt;You will be able to see two folders: backend, frontend and a README.md file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2sml3zwej3rofpvs9su.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2sml3zwej3rofpvs9su.png" alt="Project view from IDE" width="799" height="433"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  4.2 Running the Application
&lt;/h4&gt;

&lt;p&gt;Now that our database is running, let's start the application. Go to your IDE where you open the project. &lt;/p&gt;

&lt;p&gt;Go to the &lt;strong&gt;backend&lt;/strong&gt; directory and create a file &lt;strong&gt;&lt;code&gt;.env&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Copy content from &lt;code&gt;.env-example&lt;/code&gt;. And paste it in &lt;strong&gt;&lt;code&gt;.env&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now replace &lt;strong&gt;&lt;code&gt;DB_PASSWORD='&amp;lt;DB_PASSWORD&amp;gt;'&lt;/code&gt;&lt;/strong&gt; with &lt;strong&gt;&lt;code&gt;DB_PASSWORD=myrootpassword&lt;/code&gt;&lt;/strong&gt;. And &lt;strong&gt;&lt;code&gt;ALLOWED_ORIGIN=http://localhost:4501&lt;/code&gt;&lt;/strong&gt; with &lt;strong&gt;&lt;code&gt;ALLOWED_ORIGIN=http://localhost:8080&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Before proceeding, make sure you are using Node.js version &lt;strong&gt;"&amp;gt;=20.0.0"&lt;/strong&gt;. Currently I'm using the LTS version, which is &lt;strong&gt;v24.13.0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Open the terminal and run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Once you have a compatible node version, start the backend server.&lt;/p&gt;

&lt;p&gt;Open a terminal and go to the **backend **directory&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install dependencies&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;backend
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Run the migration&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Once migrated, you will see below the O/P&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Run dev server&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;If everything goes fine, you will see below the O/P&lt;/p&gt;

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

&lt;p&gt;Now, in the browser, open URL &lt;strong&gt;&lt;code&gt;http://localhost:5500/api/todos&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You will be able to see dummy todos in the browser&lt;/p&gt;

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

&lt;p&gt;Open a new terminal and go to the &lt;strong&gt;frontend&lt;/strong&gt; directory&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install dependencies&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;frontend
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Setup Env Variable:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go to the frontend directory and create a file &lt;strong&gt;&lt;code&gt;.env&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Copy content from &lt;code&gt;.env-example&lt;/code&gt;. And paste it in &lt;strong&gt;&lt;code&gt;.env&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start dev server&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Open your browser to &lt;code&gt;http://localhost:8080&lt;/code&gt;. &lt;/p&gt;

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

&lt;p&gt;If you see data loading from the API, &lt;strong&gt;congratulations!&lt;/strong&gt;🎉. Your baseline environment is set up perfectly.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;a&gt;&lt;/a&gt;Conclusion
&lt;/h3&gt;

&lt;p&gt;I hope you successfully set up the app locally and enjoyed the process. In the next part, we will learn how to containerize our frontend. Stay tune. Please post your queries if you faced any issues.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>backend</category>
      <category>devops</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>My Bulletproof CSS Template for Perfect Text Wrapping</title>
      <dc:creator>utkarsh srivastava</dc:creator>
      <pubDate>Mon, 08 Sep 2025 18:44:09 +0000</pubDate>
      <link>https://dev.to/utkarsh4321/my-bulletproof-css-template-for-perfect-text-wrapping-2p2h</link>
      <guid>https://dev.to/utkarsh4321/my-bulletproof-css-template-for-perfect-text-wrapping-2p2h</guid>
      <description>&lt;p&gt;I recently cracked the code on a CSS template that flawlessly wraps long text into any number of lines without causing overflow. It's a game-changer for clean, responsive layouts, and I'm jotting it down here for future reference. This solution ensures that text stays contained and visually appealing, regardless of the container size or content length.&lt;/p&gt;

&lt;p&gt;Refer to this sandbox link &lt;br&gt;
&lt;a href="https://codesandbox.io/p/devbox/still-frog-xw8fjj?workspaceId=ws_2AbmuvE7BZrRXWVcWYYweJ" rel="noopener noreferrer"&gt;Demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let'see&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  &amp;lt;div class="container"&amp;gt;
      &amp;lt;div class="card"&amp;gt;
        &amp;lt;h4&amp;gt;A Big Paragraph&amp;lt;/h4&amp;gt;
        &amp;lt;p&amp;gt;
          Lorem, ipsum dolor sit amet consectetur adipisicing elit. Impedit non
          repellendus error quod sunt maiores voluptatum rerum illum, sit neque?
          Lorem, ipsum dolor sit amet consectetur adipisicing elit. Temporibus
          ipsa tenetur nihil placeat soluta rerum fugiat, sequi saepe dolorem
          iste ut beatae quos! Maxime repudiandae dolor labore nulla! Modi
          adipisci est ex quaerat veritatis obcaecati illo unde nihil delectus.
        &amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div class="card"&amp;gt;
        &amp;lt;h4&amp;gt;A long word without any space&amp;lt;/h4&amp;gt;
        &amp;lt;p&amp;gt;  
jjjjjjjjjjjjjjjjdjdjdjdndndndndndjdjsaanansnsns@djejr12243nnfnfnfngn@nfgfgjrmrnn4jfmfnm$mbmbbjbmnfgnvmfmfmfm#kvkfmfmfmfm
        &amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the CSS for the card and container&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.container {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.card {
  box-shadow: 0 0px 5px rgba(0, 0, 0, 0.2);
  border-radius: 5px;
  padding: 0 20px;
  width: 40%;
}

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

&lt;/div&gt;



&lt;p&gt;It looks like&lt;/p&gt;

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

&lt;p&gt;See how the long paragraph is growing in height. And our long word is going out of the card, which is disturbing our whole grid.&lt;/p&gt;

&lt;p&gt;How to fix it? 🤔&lt;/p&gt;

&lt;p&gt;Add this 🥷 &lt;strong&gt;Ninja Technique&lt;/strong&gt; to &lt;code&gt;p&lt;/code&gt; tag&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p {
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  text-overflow: ellipsis;
  overflow-wrap: anywhere;
}

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

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-webkit-line-clamp&lt;/code&gt;, you can modify this value to adjust your wrapping to limit the number of lines shown. Any text that goes beyond this limit will be hidden. In my case, it goes up to line 2&lt;/p&gt;

&lt;p&gt;&lt;code&gt;overflow-wrap: anywhere;&lt;/code&gt; this we are using to handle long, big strings or words, like in our case. This property allows the browser to break words at any point to prevent overflow.&lt;/p&gt;

&lt;p&gt;Boom see result&lt;/p&gt;

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

&lt;p&gt;Give it a try.&lt;/p&gt;

&lt;p&gt;Follow me for more tips like this 🙌&lt;/p&gt;

</description>
    </item>
    <item>
      <title>First timer open-source contribution</title>
      <dc:creator>utkarsh srivastava</dc:creator>
      <pubDate>Mon, 09 Nov 2020 18:38:29 +0000</pubDate>
      <link>https://dev.to/utkarsh4321/first-timer-open-source-contribution-1562</link>
      <guid>https://dev.to/utkarsh4321/first-timer-open-source-contribution-1562</guid>
      <description>&lt;h3&gt;
  
  
  About me
&lt;/h3&gt;

&lt;p&gt;My name is &lt;strong&gt;utkarsh Srivastava&lt;/strong&gt;. You can call me &lt;code&gt;ut&lt;/code&gt;. I m a &lt;code&gt;front-end developer&lt;/code&gt; from India. A wish to make a little contribution in developer community brings me here. I always wish to participate in &lt;strong&gt;Hacktoberfest&lt;/strong&gt; but some hurdles always stop me. But this time I hardly want to contribute in &lt;strong&gt;hacktoberfest&lt;/strong&gt;. So finally I did it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;My &lt;strong&gt;Hacktoberfest&lt;/strong&gt; journey is wonderful. On the time of writing this article, I successfully completed my this year &lt;strong&gt;hacktoberfest&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Some steps that I took to complete this challenge
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;On the beginning of the October first, I went on &lt;code&gt;hacktoberfest&lt;/code&gt; website &lt;a href="https://hacktoberfest.digitalocean.com/"&gt;hacktoberfest&lt;/a&gt; and signup for &lt;code&gt;hacktoberfest 2020&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;I don't have any idea at this point. So I start looking and reading the instructions in &lt;strong&gt;hacktoberfest&lt;/strong&gt; website.&lt;/li&gt;
&lt;li&gt;After that, I found some contribution website(where you can search for an open-source project and work on it).&lt;/li&gt;
&lt;li&gt;So I found one i.e &lt;a href="https://goodfirstissues.com/"&gt;goodfirstissues.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I think this website is by Github I m not sure.&lt;/li&gt;
&lt;li&gt;That's empowering first-timers contributors of open-source.&lt;/li&gt;
&lt;li&gt;Then I select &lt;code&gt;language&lt;/code&gt; in my case &lt;code&gt;javascript&lt;/code&gt; to filter out projects.&lt;/li&gt;
&lt;li&gt;Then I found beginners friendly projects and start working on the open-source projects.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Contributions
&lt;/h3&gt;

&lt;p&gt;1.&lt;a href="https://github.com/devlup-labs/meet-scheduler"&gt;Meet-scheduler&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is an amazing &lt;code&gt;Chrome extension&lt;/code&gt; that helps to manage google meet links.&lt;/li&gt;
&lt;li&gt;I choose this project because I wanted to learn about how to develop a chrome extension. And the &lt;code&gt;tech stack&lt;/code&gt; they use also matches my skills set.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2.&lt;a href="https://github.com/RyanRussell00/personal-dj"&gt;Personal-dj&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is a personal-DJ website that helps us to create &lt;code&gt;playlist&lt;/code&gt;
directly in our &lt;strong&gt;Spotify&lt;/strong&gt; account.&lt;/li&gt;
&lt;li&gt;I choose this project because I always wanted to create online streaming type application.&lt;/li&gt;
&lt;li&gt;So in order to take some idea. I choose this project because it is built with &lt;a href="https://developer.spotify.com/documentation/web-api/"&gt;spotify API&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3.&lt;a href="https://github.com/Abdur-rahmaanJ/shopyo"&gt;Shopyo&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's a point of sales software for small shops.&lt;/li&gt;
&lt;li&gt;Build on python and flask&lt;/li&gt;
&lt;li&gt;I choose this project to learn about flask.&lt;/li&gt;
&lt;li&gt;But contributor told me to fix some small changes so I did that and they merge my PR.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4.&lt;a href="https://github.com/learn-opensource/learn-opensource.github.io"&gt;Learn-opensource&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's basically a great contribution driven project which helps new developers to get familiar with open source.&lt;/li&gt;
&lt;li&gt;I choose this project because it's built on &lt;code&gt;javascript&lt;/code&gt;. And it's a little bit challenging to show different API's response into one page.&lt;/li&gt;
&lt;li&gt;So I worked on issue page where I have to show all the open issues of their organization. So I added loader and after fetching data from &lt;code&gt;GitHub API&lt;/code&gt;. I render it on that page.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tips
&lt;/h3&gt;

&lt;p&gt;One thing I notice whenever you start contributing to an open-source project. Please talk to repo owner as much as you can and try to understand their requirements what they really want.&lt;br&gt;
And don't hesitate to ask because they are so helpful they will help you as much as they can. So please talk to them before you start working on it. It will save lots of time. So that you can easily submit &lt;strong&gt;4 PR's&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reflections
&lt;/h3&gt;

&lt;p&gt;That's it, guys. This is all about my &lt;strong&gt;hacktoberfest&lt;/strong&gt; journey. Overall experience is awesome. I have made new connections. Also, learn about so many interesting things. I enjoy this &lt;strong&gt;October&lt;/strong&gt; like an open-source festival. Thanks to &lt;strong&gt;open-source&lt;/strong&gt; community to help me and give me such a great experience. And I m surely participate again in &lt;strong&gt;hacktoberfest&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So thank you guys I hope it's helpful.&lt;/p&gt;

&lt;p&gt;Connect with me:&lt;br&gt;
&lt;a href="https://github.com/utkarsh4321"&gt;Github&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.linkedin.com/in/utkarsh-srivastava-214108155/"&gt;Linked-in&lt;/a&gt;&lt;/p&gt;

</description>
      <category>hacktoberfest</category>
      <category>opensource</category>
      <category>goodfirstissue</category>
      <category>github</category>
    </item>
  </channel>
</rss>
