Dockerizing a Next.js Application with GitHub Actions
In this article, we'll explore how to Dockerize a Next.js application and automate its deployment using GitHub Actions, thereby simplifying the deployment workflow and enhancing development productivity.
Prerequisites
Before we dive into Dockerizing our Next.js application and setting up GitHub Actions for deployment, ensure you have the following prerequisites:
- A Next.js project.
- Docker installed on your local machine.
- A GitHub repository for your Next.js project.
Setting up Docker
Docker allows you to package your application and its dependencies into a container, ensuring consistency across different environments. Start by creating a Dockerfile
in the root of your Next.js project:
yamlCopy codeFROM node:18-alpine AS base # Install dependencies only when needed FROM base AS deps # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. RUN apk add --no-cache libc6-compat WORKDIR /app # Install dependencies based on the preferred package manager COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ RUN \ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ elif [ -f package-lock.json ]; then npm ci; \ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ else echo "Lockfile not found." && exit 1; \ fi # Rebuild the source code only when needed FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . # Next.js collects completely anonymous telemetry data about general usage. # Learn more here: https://nextjs.org/telemetry # Uncomment the following line in case you want to disable telemetry during the build. # ENV NEXT_TELEMETRY_DISABLED 1 RUN \ if [ -f yarn.lock ]; then yarn run build; \ elif [ -f package-lock.json ]; then npm run build; \ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \ else echo "Lockfile not found." && exit 1; \ fi # Production image, copy all the files and run next FROM base AS runner WORKDIR /app ENV NODE_ENV production # Uncomment the following line in case you want to disable telemetry during runtime. # ENV NEXT_TELEMETRY_DISABLED 1 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public # Set the correct permission for prerender cache RUN mkdir .next RUN chown nextjs:nodejs .next # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static USER nextjs EXPOSE 3000 ENV PORT 3000 # set hostname to localhost ENV HOSTNAME "0.0.0.0" # server.js is created by next build from the standalone output # https://nextjs.org/docs/pages/api-reference/next-config-js/output CMD ["node", "server.js"]
This Dockerfile is the default Dockerfile provided by Vercel to set up a Node.js environment, install dependencies, build the Next.js application, and exposing port 3000.
You have to ensure you are using output: "standalone"
in your next.config.js
.
javascriptCopy codeconst nextConfig = { output: "standalone", }
Before proceeding further, it's crucial to test our Dockerized Next.js application locally to ensure everything functions as expected. Open a terminal in the project directory and execute the following commands:
bashCopy code# Build the Docker image docker build -t my-nextjs-app . # Run the Docker container docker run -p 3000:3000 my-nextjs-app
Visit http://localhost:3000
in your web browser to verify that your Next.js application is running within the Docker container.
Setting up GitHub Actions for Continuous Deployment:
GitHub Actions automate the CI/CD pipeline directly from your GitHub repository. Create a .github/workflows/pipeline.yml
file with the following content if you want to publish your docker images to Docker Hub and GitHub. If you just want to use one of them you have to remove the according login step and remove the according tags.
yamlCopy codename: Docker Build & Publish on: push: branches: [main] jobs: push_to_registries: name: Push Docker image to multiple registries runs-on: ubuntu-latest permissions: packages: write contents: read attestations: write id-token: write steps: - name: Check out repository code 🛎️ uses: actions/checkout@v4 - name: Set up Docker Buildx 🚀 uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub 🚢 uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_HUB_USERNAME}} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN}} - name: Log in to the Container registry uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push 🏗️ uses: docker/build-push-action@v2 with: context: . file: ./Dockerfile push: true tags: | ${{ secrets.DOCKER_HUB_USERNAME}}/{docker_repository}:${{ github.sha }} ${{ secrets.DOCKER_HUB_USERNAME}}/{docker_repository}:latest ghcr.io/${{ github.repository }}:${{ github.sha }} ghcr.io/${{ github.repository }}:latest
If you want to publish to Docker Hub you have to store secrets ${{ secrets.DOCKER_USERNAME }}
and ${{ secrets.DOCKER_PASSWORD }}
in your repository's under settings
-> Secrets and variables
-> Actions
-> Repository secrets
.
This workflow will build your container from your GitHub repositiory and push it to your Docker Container registry with two tags:
:latest
and:{github_sha}
Passing environment variables to the Workflow
In case you need some environment variables you have to adjust the Dockerfile
with some additional parameters. To be able to use environment variables which are stored in your repository as secrets you will need to mount and export every environment variable like the following to your npm run build
command.
yamlCopy codeRUN --mount=type=secret,id=NEXT_PUBLIC_CMS_URL \ export NEXT_PUBLIC_CMS_URL=$(cat /run/secrets/NEXT_PUBLIC_CMS_URL) && \ npm run build
Also you will need to modify the step Build and push
in the workflow like this:
yamlCopy code- name: Build and push 🏗️ uses: docker/build-push-action@v2 with: context: . file: ./Dockerfile push: true tags: | ${{ secrets.DOCKER_HUB_USERNAME}}/personal-website:${{ github.sha }} ${{ secrets.DOCKER_HUB_USERNAME}}/personal-website:latest secrets: | "NEXT_PUBLIC_STRAPI_API_URL=${{ secrets.NEXT_PUBLIC_CMS_URL }}"
Conclusion
With this setup, every push to the main branch of your GitHub repository triggers the CI/CD pipeline. Continuous Integration and Continuous Deployment for Dockerized Next.js applications provide a streamlined and efficient development process, ensuring that your application is always in a deployable state. By combining GitHub Actions with Docker, you can automate the deployment process and focus on building and improving your Next.js application.
First published February 6, 2024
Have you published a response to this? Send me a webmention by letting me know the URL.
Found no Webmentions yet. Be the first!
About The Author
Geospatial Developer
Hi, I'm Max (he/him). I am a geospatial developer, author and cyclist from Rosenheim, Germany. Support me
Continue Reading
Optimizing images for Next.js sites with imgproxy and docker
How to transform and optimize images with imgproxy hosted with docker for your Next.js application.Building a Table of Contents (TOC) from markdown for your React blog
How to create a Table of Contents (TOC) from markdown for your React blog with Javascript without any third party dependencies.How to create a custom cookie banner for your React application
Recently I implemented a custom cookie banner solution on my Next.js site which you probably have seen a few seconds before. There are a lot of prebuilt cookie banners you can use for React or Next js sites but i wanted to create a custom cookie banner which also has some personal touch and keeps the design with the website in line.