DEV Community

Rodolphe Dupuis
Rodolphe Dupuis

Posted on

Implementing Docker for a Next.js and Node.js App: A Step-by-Step Guide πŸ³πŸš€

In today’s development landscape, containerization is revolutionizing how applications are built, shipped, and run. Tools like Docker simplify deployment, improve scalability, and ensure consistency across environments.

In this article, we’ll explore how to use Docker to containerize a Next.js frontend and a Node.js backend, highlighting the many benefits Docker brings to modern development workflows. 🐳

We will use one of the app I developed for one of my previous guide:

Here we go! πŸš€

Why use Docker?

First of all, it's important to understand why we use this tool.

Docker provides several advantages for developers and businesses:

  • Environment Consistency: Ensures your app runs identically on development, staging, and production environments.
  • Simplified Deployment: Packaged containers make deployments repeatable and scalable.
  • Dependency Management: Bundles dependencies into containers, eliminating "it works on my machine" issues.
  • Scalability: Containers are lightweight and can be scaled horizontally with ease.
  • Isolation: Containers keep your application isolated from the host system, improving security and stability.

Setting Up Docker for a Next.js and Node.js Application

Prerequisites

  1. Install Docker
  2. Basic understanding of Docker concepts
project/
β”œβ”€β”€ jwt-back/
β”‚   β”œβ”€β”€ package.json
β”‚   β”œβ”€β”€ server.js
β”‚   └── Dockerfile
β”œβ”€β”€ jwt-front/
β”‚   β”œβ”€β”€ package.json
β”‚   β”œβ”€β”€ next.config.js
β”‚   └── Dockerfile
β”œβ”€β”€ docker-compose.yml
Enter fullscreen mode Exit fullscreen mode

Step 1: Containerize the Node.js Backend

We created this previous package.json in the backend app:

{
  "name": "jwt-back",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "author": "Rodolphe Dupuis",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "bcryptjs": "^2.4.3",
    "body-parser": "^1.20.3",
    "cookie-parser": "^1.4.7",
    "cors": "^2.8.5",
    "express": "^4.21.1",
    "jsonwebtoken": "^9.0.2"
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's add the Dockerfile:

# Use Node.js as the base image
FROM node:18

# Set the working directory
WORKDIR /app

# Copy the package.json file and install its dependencies
COPY package.json .
RUN npm install

# Copy the rest of the application code
COPY . .

# Expose the port the app will run on
EXPOSE 5001

# Start the server
CMD ["npm", "start"]
Enter fullscreen mode Exit fullscreen mode

Now that we are all set with the backend Dockerfile, we can build and run it:

docker build -t backend-app ./backend
docker run -p 3001:3001 backend-app
Enter fullscreen mode Exit fullscreen mode

So far everything looks good and the backend app is running smoothly.


Step 2: Containerize the Next.js Frontend

βš›οΈ We created this previous package.json in the frontend app when creating the Next.js app:

{
  "name": "jwt-front",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "axios": "^1.7.8",
    "next": "15.0.3",
    "react": "19.0.0-rc-66855b96-20241106",
    "react-dom": "19.0.0-rc-66855b96-20241106"
  },
  "devDependencies": {
    "eslint": "^8",
    "eslint-config-next": "15.0.3"
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's add the Dockerfile:

# Use Node.js as the base image
FROM node:18

# Set the working directory
WORKDIR /app

# Copy package.json and install dependencies
COPY package.json .
RUN npm install

# Copy the rest of the application code
COPY . .

# Build the app
RUN npm run build

# Expose the port for Next.js
EXPOSE 3001

# Start the app
CMD ["npm", "start"]
Enter fullscreen mode Exit fullscreen mode

Now that we are all set with the frontend Dockerfile, we can build and run it:

docker build -t frontend-app ./frontend
docker run -p 3000:3000 frontend-app
Enter fullscreen mode Exit fullscreen mode

At this step, your frontend app is running using Docker perfectly as well as your backend app


Step 3: Orchestrate with Docker Compose

Now, let's break into one of the most important and powerful part of Docker: orchestrating. Docker Compose simplifies the process of managing multi-container applications.

Let's add a docker-compose.yml file at the root of the project (check the project structure again if needed):

version: '3.8'
services:
  frontend:
    build: ./jwt-front
    ports:
      - "3000:3000"
    depends_on:
      - backend

  backend:
    build: ./jwt-back
    ports:
      - "5001:5001"
Enter fullscreen mode Exit fullscreen mode

Now, you can run the application with Docker Compose using the following command:

docker-compose up --build
Enter fullscreen mode Exit fullscreen mode

Now you can freely access your frontend app at http://localhost:3000 and interact with the server!


Benefits of Using Docker for This Setup

We are almost at the end of this guide and maybe you are wondering what is the purpose behind all that. After all, you are still accessing your frontend app the same way right?

Let's break down the benefits of Docker:

  1. Simplified Development Environment
    Docker ensures that both the frontend and backend run in isolated, consistent environments, eliminating setup hassles.

  2. Portability
    The entire application can be packaged and run on any machine with Docker, regardless of the host OS πŸ“¦

  3. Scalability
    Using tools like Kubernetes, these containers can be easily scaled to handle high traffic πŸ› οΈ

  4. Efficient Resource Usage
    Containers are lightweight and start quickly, improving performance compared to traditional virtual machines 🌐

  5. Ease of Testing
    Spin up identical containers for staging or testing without impacting the production environment 🚒

  6. Seamless Collaboration
    Teams can share Docker images or configurations, ensuring everyone is working on the same environment πŸš€


Best Practices for Dockerizing Your App

You can use .dockerignore to exclude unnecessary files from your image (e.g., node_modules or .git directories).

Keep Docker images lightweight by using multistage builds. Multistage builds make use of one Dockerfile with multiple FROM instructions. Each of these FROM instructions is a new build stage that can COPY artifacts from the previous stages.

Regularly update your base images to include the latest security patches.

Use environment variables for sensitive data, avoiding hardcoding them into Dockerfiles.


Conclusion

Implementing Docker for your Next.js and Node.js app brings reliability, scalability, and simplicity to your development workflow. By containerizing both the frontend and backend, you streamline deployment, maintain environment consistency, and unlock the potential for rapid scaling.

Docker is a must-have tool for modern developersβ€”start using it today and elevate your application development process! πŸš€

Let me know if there is a concept or tech that you would like me to talk about!

Happy coding!

Top comments (0)