<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Michelle Mendoza</title>
    <description>The latest articles on Forem by Michelle Mendoza (@miannemendoza).</description>
    <link>https://forem.com/miannemendoza</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2967564%2F1e8c8651-bd18-40ec-ba84-d13f5f15d890.png</url>
      <title>Forem: Michelle Mendoza</title>
      <link>https://forem.com/miannemendoza</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/miannemendoza"/>
    <language>en</language>
    <item>
      <title>How to: Automate Your Next.js Docker App with Render and GitHub Actions</title>
      <dc:creator>Michelle Mendoza</dc:creator>
      <pubDate>Mon, 03 Nov 2025 15:47:00 +0000</pubDate>
      <link>https://forem.com/miannemendoza/how-to-automate-your-nextjs-docker-app-with-render-and-github-actions-51c2</link>
      <guid>https://forem.com/miannemendoza/how-to-automate-your-nextjs-docker-app-with-render-and-github-actions-51c2</guid>
      <description>&lt;p&gt;Serverless deployment like vercel are great for speed and ease of use but they come with limits, your functions can’t stay alive and do background work. If you want to create a simple function that will run every hour, it will be impossible without having to create a workaround which can be frustrating if you just want to implement it within your code.&lt;/p&gt;

&lt;p&gt;The solution to these serverless limits is to deploy your application as a Docker image. Unlike serverless environments where your function will stop after execution, a Docker container runs as a persistent server, allowing you to run background functions or scheduled tasks without the risk of termination.&lt;/p&gt;

&lt;p&gt;In my previous &lt;a href="https://dev.to/miannemendoza/how-to-use-nextjs-standalone-for-leaner-docker-images-2kn9"&gt;article&lt;/a&gt;, I wrote a tutorial on how to create a leaner, optimized Dockerized image in your Next.js app. While this gives you the leaner and optimized image, your next question is probably “Where do I use this now?”. There’s a lot of options to choose from like AWS which is widely known, but for someone who is just starting to learn how to connect these things, it can be overwhelming.&lt;/p&gt;

&lt;p&gt;Which is why in this tutorial, I will introduce Render, a powerful and developer-friendly platform that's a cost-effective alternative to AWS. Its free tier is perfect for deploying and testing the exact production-level workflow that we'll build in this guide. &lt;/p&gt;

&lt;p&gt;We will also setup our Github workflow to do these 3 things.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Automatically trigger a docker image build when a branch has been merged to main (or your branch of choice)&lt;/li&gt;
&lt;li&gt;Push the docker image to docker registry when it finishes building.&lt;/li&gt;
&lt;li&gt;Lastly, when pushing to registry is successful, we will trigger a webhook from render that would automatically deploy the docker image.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This would finally automate your deployment pipeline for your Next.js app.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚨 Prerequisites
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A Dockerized Next.js App:&lt;/strong&gt; Your app must be Dockerized using the &lt;code&gt;standalone&lt;/code&gt; output. (If you haven't done this, you can follow my guide &lt;a href="https://dev.to/miannemendoza/how-to-use-nextjs-standalone-for-leaner-docker-images-2kn9"&gt;here&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Docker Hub Account&lt;/strong&gt; with:

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;public repository&lt;/strong&gt; created to host your image.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Personal Access Token (&lt;a href="https://docs.docker.com/security/access-tokens/" rel="noopener noreferrer"&gt;PAT&lt;/a&gt;)&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Manually Pushed Image:&lt;/strong&gt; As a final setup step, you must &lt;strong&gt;manually build and push your first image&lt;/strong&gt; to your new Docker Hub repository. This is required for Render to find and deploy your image in Step 1.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🛠️ Walkthrough
&lt;/h2&gt;

&lt;p&gt;This guide is a two-part plan. First, we'll do it manually by deploying our existing Docker image to Render. This is a crucial step because Render requires a valid, existing image in order to create our new service. Second, once our service is live, we'll automate the entire process by building a complete CI/CD pipeline in GitHub Actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Manually Deploying Your Image to Render
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Head on to &lt;a href="https://dashboard.render.com/register" rel="noopener noreferrer"&gt;render&lt;/a&gt; to create your account. After registering and personalization questions, you can now add a new service.&lt;/li&gt;
&lt;li&gt;Within the list, look for webservice. 
&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%2F4aw4v7b6j94akxmteliu.png" alt="List of services offered by render" width="800" height="299"&gt;
&lt;/li&gt;
&lt;li&gt;After selecting webservice, you will see 3 options, but pick the existing image. 
&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%2F9vwss1q86pbqdcg3dz5g.png" alt="Options to choose where the repository/image will come from" width="800" height="299"&gt;
&lt;/li&gt;
&lt;li&gt;This will show you the field to enter your docker image reference that you want to deploy. Ensure that your image is set to public to complete this process without adding your credentials. Once you are done, click connect.&lt;/li&gt;
&lt;li&gt;It will now allow you to set your own webservice name and pick a region and payment tier. For this tutorial, we will be using free tier.
&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%2Fw3v4riddgbrn7hh67w54.png" alt="Image of the part where you can set webservice name and change tier " width="800" height="299"&gt;
&lt;/li&gt;
&lt;li&gt;Then below the Instance Type section is the environment variables. You can add your environment variables here if you have one. If you not, you can just skip and click deploy web service.
&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%2Fyx90tj5ueecvhw3z5nf5.png" alt="Part where you can set your environment variables" width="800" height="195"&gt;
&lt;/li&gt;
&lt;li&gt;You will be redirected to the logs screen of your webservice where you will see the status of deployment. After the successful deployment, you should be able to visit your deployed app.&lt;/li&gt;
&lt;li&gt;Now that the deployment part is done, navigate to the settings. Head to the deploy section and you will see the &lt;strong&gt;Deploy Hook.&lt;/strong&gt; Copy your deploy hook and save it somewhere as we will be using this later when we configure your GitHub CI/CD.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 2: Automating your docker image build and deploying to render
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;After completing the setup on Render, head over to your github repository where your project is located. Go to &lt;code&gt;settings/security/secrets&lt;/code&gt; and &lt;code&gt;variables/actions&lt;/code&gt;. We need to add the secrets and variables so that our workflow file can use them.&lt;br&gt;
a. Create your repository secrets&lt;br&gt;
First, click on the secrets tab and select “New repository secret”. Then create the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DOCKER_PASSWORD&lt;/code&gt;&lt;/strong&gt;: Paste your Docker PAT (Personal Access Token) here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;RENDER_WEBHOOK&lt;/code&gt;&lt;/strong&gt;: Paste your Deploy Hook URL that you saved from Render.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;b. Create your repository variables&lt;br&gt;
    Click the variables tab (right next to secrets) and create the following variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DOCKER_USERNAME&lt;/code&gt;&lt;/strong&gt;: Your Docker Hub username.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DOCKER_REPO_NAME&lt;/code&gt;&lt;/strong&gt;: The name of your Docker Hub repository (e.g., &lt;code&gt;my-next-app&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now that the secrets and variables are in place, go to your .yml file (Or if you don’t have one, create it under .github/workflows in your root folder) and  apply the following code:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Deploy Pipeline&lt;/span&gt;
&lt;span class="na"&gt;run-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build &amp;amp; Deploy run triggered by ${{ github.actor }}&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;closed&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;develop&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;build-and-push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repository code&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v5&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Extract docker image metadata&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meta&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/metadata-action@v5&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{vars.DOCKER_USERNAME}}/${{vars.DOCKER_REPO_NAME}}&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Log in to Docker Hub&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v3&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ vars.DOCKER_USERNAME }}&lt;/span&gt;
      &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKER_PASSWORD }}&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Docker Buildx&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/setup-buildx-action@v3&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and push docker images&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v6&lt;/span&gt;
            &lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.meta.outputs.tags }}&lt;/span&gt;
              &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Notify webhook&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;curl -f -X POST ${{ secrets.RENDER_WEBHOOK }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;strong&gt;Here are the key takeaway of this &lt;code&gt;.yml&lt;/code&gt; file&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;on: pull_request:&lt;/strong&gt; In this .yml file, we have set up a condition that will only trigger once the condition is met. If the &lt;code&gt;pull_request&lt;/code&gt; has been merged and closed in the develop branch, then the workflow will run. You are free to set up your own condition here depending on your needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checkout repository code:&lt;/strong&gt; Here we clone your repository in the runner’s workspace to allow it to be accessed in the next step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract docker image metadata:&lt;/strong&gt; In this step, we will use docker/metadata-action@v5 to generate smart image tags based on the GitHub event. For example, it will tag this build with the develop branch name, which is why our build-and-push step can just use &lt;code&gt;${{ steps.meta.outputs.tags }}&lt;/code&gt; without us hardcoding any tag names.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Login in to Docker Hub:&lt;/strong&gt; This step securely logs into Docker Hub using the &lt;code&gt;DOCKER_USERNAME&lt;/code&gt; and &lt;code&gt;DOCKER_PASSWORD&lt;/code&gt; secrets we created. This is a required step before our workflow is allowed to push the new image to the registry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set up Docker Buildx / Build and push Docker images:&lt;/strong&gt; In these steps, we configure the Docker Buildx builder and then use it to build the image and push it to the registry. The generated metadata earlier will be used here to define the image tag.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notify Webhook:&lt;/strong&gt; This is the final step that will call the webhook from render. This will allow the automatic deployment of the new image that just got pushed to the registry. Do not forget the -f flag in this step, it will notify you if reaching the webhook fails which will help for debugging.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now that everything is in place, you can test out the flow by creating a pull request and merging it to your develop. After doing that, head over to the actions tab in your GitHub repository and you should see the CI/CD steps get executed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Head over to render to verify that a deployment has been triggered and now you just need to wait for it to be completed. Once the deployment is successful, refresh your live app’s URL to see your new changes in production!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;And there you have it 🎉 You have successfully automated the build process that would normally take 3 or 4 manual steps to do it. By deploying your Next.js app on a proper server, you can now create functions that can run and operate on intervals without the worry that it will stop.&lt;/p&gt;

&lt;p&gt;How did your deployment go? Did you get your pipeline up and running? If you hit any issues or have a pro-tip to share, drop it in the comments below!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>nextjs</category>
      <category>githubactions</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to: Use Next.js Standalone for Leaner Docker Images</title>
      <dc:creator>Michelle Mendoza</dc:creator>
      <pubDate>Mon, 20 Oct 2025 12:00:00 +0000</pubDate>
      <link>https://forem.com/miannemendoza/how-to-use-nextjs-standalone-for-leaner-docker-images-2kn9</link>
      <guid>https://forem.com/miannemendoza/how-to-use-nextjs-standalone-for-leaner-docker-images-2kn9</guid>
      <description>&lt;p&gt;Building a Docker image from a fresh Next.js app can easily bloat your output to over &lt;strong&gt;890+ MB&lt;/strong&gt; and that size comes at a cost. Bigger images mean slower CI/CD pipelines, thanks to the extra time needed to pull and push them. &lt;/p&gt;

&lt;p&gt;But, this can be resolved with the introduction of the &lt;strong&gt;standalone&lt;/strong&gt; feature of Next.js. What standalone does is it creates a &lt;code&gt;.next/standalone&lt;/code&gt; folder that packages only the production-critical files and excludes dev dependencies and unnecessary files. In this walkthrough, I will show you how I was able to achieve a 75% reduction in my image’s size by leveraging the standalone feature of Next.js in a multi-stage docker build. &lt;/p&gt;




&lt;h2&gt;
  
  
  🚨 Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An existing Next.js app (Minimum required version is Next.js v12.2.0) or if you don’t have one, you can quickly create with &lt;code&gt;npx create-next-app@latest&lt;/code&gt; .&lt;/li&gt;
&lt;li&gt;A Docker installed in your machine. (If you currently don’t have one, you can follow this official guide form Docker on how to &lt;a href="https://docs.docker.com/engine/install/" rel="noopener noreferrer"&gt;install&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🛠️ Walkthrough
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Enable Standalone Output
&lt;/h3&gt;

&lt;p&gt;First, open your &lt;code&gt;next.config.ts&lt;/code&gt; file in the root directory of your project and add &lt;code&gt;output: "standalone"&lt;/code&gt;. This enables your Next.js app to build with only the essential files.&lt;/p&gt;

&lt;p&gt;Your &lt;code&gt;next.config.ts&lt;/code&gt; file should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; const nextConfig = {
   // Your other Next.js configurations can go here
   output: "standalone",
 };

 export default nextConfig;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Create the Multi-Stage Dockerfile
&lt;/h3&gt;

&lt;p&gt;I’ve prepared a &lt;strong&gt;Dockerfile&lt;/strong&gt; for you to use. In this walkthrough, we’ll be using a multi-stage Docker build. Multi stage builds are a good practice because it creates a smaller and much secure docker image by separating the builder stage and runner stage. Here’s a quick explanation of what a multi-stage build does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The builder stage:&lt;/strong&gt; This is where we install all dependencies including dev dependencies and run the build process to generate next/standalone output. Essentially, this stage produces everything that is required for the production build.&lt;/li&gt;
&lt;li&gt;The runner stage: This is the part where we create the final, production ready image. It contains only the minimal files needed to serve the app, resulting in a much smaller image. No dev dependencies, no full node modules, strictly only what is needed.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM node:20-alpine AS builder
WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .
RUN npm run build

# 2. Runner Stage
# Runner Stage
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV HOSTNAME=0.0.0.0
ENV PORT=10000

EXPOSE 10000

COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public

# no entrypoint needed
CMD ["node", "server.js"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Here are the key takeaways of this Dockerfile:&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cache trick: Running &lt;code&gt;npm install&lt;/code&gt; before copying the source code allows you to take advantage of Docker’s layer caching system. If anything changes in your code but not on the dependencies then this part will not run again, saving us some time.&lt;/li&gt;
&lt;li&gt;Environment variables: We set environment variables to define the app’s runtime behavior. By adding &lt;code&gt;NODE_ENV=production&lt;/code&gt; on your file, you are telling Next.js to run in the optimize production mode. Setting hostname to 0.0.0.0 is essential for a container as it allows it to listen to any outside connection.&lt;/li&gt;
&lt;li&gt;The three essential COPY: These set of COPY commands is necessary in order to optimize our build. It is a common pitfall that the &lt;code&gt;next/standalone&lt;/code&gt; folder contains everything. But by default, &lt;code&gt;.next/standalone&lt;/code&gt; does not include your static and public folder. That is why we have to copy it in order to include it in the final build.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Build and Run Your Image
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Build your image using this command: docker build -t my-app .&lt;/li&gt;
&lt;li&gt;Run your image using this command: docker run -p 3000:10000 my-app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can now access your app at &lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;http://localhost:3000/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Note: The -p 3000:10000 flag maps your local machine's port 3000 to the containers internal port 10000. This allows you to modify how you access the app without changing the internal configuration.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  📝 Conclusion
&lt;/h2&gt;

&lt;p&gt;And there you have it! By combining a multi-stage build with the standalone feature, we've taken a standard 892mb Next.js image and reduced it to 229mb, a &lt;strong&gt;75% reduction&lt;/strong&gt; 🤯. While this number may vary depending on the size of your actual project, this proves that you can significantly optimize your image by using this setup, leading to a faster build and deployment times in your CI/CD pipelines.&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;Without standalone (multistage build)&lt;/em&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%2F6xfkh6927odc5putqy49.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%2F6xfkh6927odc5putqy49.png" alt="Without standalone image build size" width="800" height="57"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;With standalone (multistage build)&lt;/em&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%2F9miq0hf6nov7xe2f58kn.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%2F9miq0hf6nov7xe2f58kn.png" alt="With standalone image build size" width="800" height="59"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do you have any tips or tricks to further optimize your docker image build 🤔? Comment down below and share it with the others!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>docker</category>
      <category>tutorial</category>
      <category>performance</category>
    </item>
    <item>
      <title>GitHub Actions 101: Your First Step Into CI/CD</title>
      <dc:creator>Michelle Mendoza</dc:creator>
      <pubDate>Mon, 06 Oct 2025 11:35:28 +0000</pubDate>
      <link>https://forem.com/miannemendoza/github-actions-101-your-first-step-into-cicd-674</link>
      <guid>https://forem.com/miannemendoza/github-actions-101-your-first-step-into-cicd-674</guid>
      <description>&lt;p&gt;Have you ever noticed job postings that say &lt;em&gt;“Knowledge of CI/CD or GitHub Actions required”&lt;/em&gt; or have you come across it and wonder “What does that actually mean?” If so, you’re in the right place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Continuous Integration and Continuous Deployment (CI/CD)&lt;/strong&gt; is the practice of automatically building, testing, and deploying code changes, which allows a faster and reliable software release. In real life, it’s like ordering a food from a high-tech food app. You first place your order (push code) then the automated kitchen builds your meal (cooks it), runs a quality check on the food (tests it) and a driver instantly brings your order to your doorstep (deploys it).&lt;/p&gt;

&lt;p&gt;If you’re just starting to explore these topics, CI/CD can sound intimidating, but adding your first GitHub Action is easier than you think! In this walkthrough, I’ll show you how to set up a simple GitHub Action so that every time you push your code changes, it will automatically run without your intervention.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚨 Prerequisites:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This guide assumes you already have a Next.js app in a GitHub repository. If not, you can quickly create with &lt;code&gt;npx create-next-app@latest&lt;/code&gt; and push it to GitHub.&lt;/li&gt;
&lt;li&gt;If you see a banner in your repository that asks you to add a billing, you will need to input a valid card payment in order to proceed.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🛠️ The Walkthrough
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;We'll tackle this in four steps: create the file, add the code, push to GitHub, and check the results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt;: Create a folder in your root directory called &lt;code&gt;.github&lt;/code&gt;. Once you have that, create a new folder inside it named &lt;code&gt;workflows&lt;/code&gt;. This is where we will add our GitHub action file. In this walkthrough, I will name my GitHub action file &lt;code&gt;github-action-demo.yml&lt;/code&gt;. You are free to name yours whatever fits your needs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;💡  This directory is where github scans for an available github actions. Any &lt;code&gt;.yml&lt;/code&gt; file that you put in here will be detected as a github action.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;: Inside the .yml file we created, we will define a basic github actions workflow configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: A Simple Hello World
on: [push]
jobs:
  say-hello-job:
    runs-on: ubuntu-latest
    steps:
      - name: Say Hello
        run: echo "Hello, World! I'm running my first GitHub Action!"
      - name: Say Goodbye
        run: echo "Done! This was easy."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Name: This defines the name of the workflow. It allows you to easily identify it in the Github Actions dashboard. You can also use &lt;code&gt;name&lt;/code&gt; inside individual steps to describe the action that they are performing.&lt;/li&gt;
&lt;li&gt;on: [push] = This defines the event that triggers the action. In this example, the workflow will run everytime you push a code to your repository.

&lt;ul&gt;
&lt;li&gt;Github Actions supports many other triggers like (push, pull_request etc.) you can check out the other events in here: &lt;a href="https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows" rel="noopener noreferrer"&gt;Github actions event&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Jobs: This is where you will define what your workflow actually does. A github action can have multiple jobs. By default, job runs  in parallel unless you specify a dependency, in which case one job will only run after another completes. In my example, we will only have 1 job.

&lt;ul&gt;
&lt;li&gt;To avoid confusion:

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;Job&lt;/strong&gt; is a set of instructions. For example, "How to bake a cake".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Steps&lt;/strong&gt; are the sequential things that you need to follow in order to have a perfect cake.

&lt;ol&gt;
&lt;li&gt;"Preheat the oven to 350°F."&lt;/li&gt;
&lt;li&gt;"Mix flour, sugar, and eggs."&lt;/li&gt;
&lt;li&gt;"Pour batter into pan."&lt;/li&gt;
&lt;li&gt;"Bake for 30 minutes."&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt;: Once you are done, push the file to your GitHub repository. If you have not set up your GitHub repository, refer to this official guide: &lt;a href="https://docs.github.com/en/repositories/creating-and-managing-repositories/quickstart-for-repositories" rel="noopener noreferrer"&gt;GitHub Docs QuickStart for repositories&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt;: Now go to GitHub → The repository of your app and navigate to actions. You should see an action running here (indicated by a spinner icon).&lt;/p&gt;

&lt;p&gt;✅ = This means that your GitHub action has run successfully &lt;/p&gt;

&lt;p&gt;❌ = This means that your action has failed. Click on it to check the logs for why it failed (this could be an error in your &lt;code&gt;.yml&lt;/code&gt; file or an account issue, like the billing notice mentioned in the prerequisites)&lt;/p&gt;

&lt;p&gt;And there you have it 🎉. You've just automated your first workflow. You now have a foundational skill that you can build on, such as expanding your configuration to include automated testing and many more.&lt;/p&gt;

&lt;p&gt;As a next step, try modifying the &lt;code&gt;.yml&lt;/code&gt; file so that it will run on a &lt;code&gt;pull_request&lt;/code&gt; that has been closed instead of on &lt;code&gt;push&lt;/code&gt;. Comment down below and share how you would achieve that.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>tutorial</category>
      <category>learning</category>
      <category>githubactions</category>
    </item>
  </channel>
</rss>
