SDS-Hands-on-Tutorial-With-Docker

SDS Toolbox - Hands-on Tutorial With Docker

The Software Design School (SDS) Toolbox is a collection of guides and resources to help you get started with the various tools and technologies used in software engineering.

Objective

This guide aims to enable you to use Docker as an integral part of the development process for a Node.js web application. The focus is on providing the skills necessary to compile, run, and manage code with Docker containers, highlighting Docker’s role as a versatile development toolbox that enhances workflow consistency and efficiency.

Introduction

Overview of Docker

Docker is an open platform for developing, shipping, and running applications.

Docker allows you to separate your applications from your infrastructure so you can deliver software quickly. With Docker, you can manage your infrastructure in the same ways you manage your applications.

By taking advantage of Docker’s methodologies for shipping, testing, and deploying code quickly, you can significantly reduce the delay between writing code and running it in production.

Reference Docker Docs

Overview of Node.js

Node.js is a powerful, open-source JavaScript runtime environment that enables developers to execute JavaScript code server-side. Renowned for its efficiency and scalability, Node.js operates on the V8 engine, allowing for high-speed execution of JavaScript outside the web browser.

It adopts an event-driven, non-blocking I/O model, making it particularly well-suited for building scalable network applications like web servers, real-time communication systems, and API services.

Node.js comes with npm, a vast package manager, enriching its ecosystem with a wide array of libraries and tools. This cross-platform environment is favored for its ability to handle concurrent requests efficiently, making it a popular choice for modern web development, especially in applications requiring real-time capabilities.

With a robust community, Node.js has become a staple in the technology stacks of many companies and developers worldwide.

Reference Node.js Website

Why Node.js and Docker

The objective of this tutorial is to empower you with the ability to integrate Docker effectively into the development process of a Node.js web application. Here’s an overview of why Docker and Node.js are pivotal in this hands-on session:

Enhancing Development and Workflow Consistency: Docker’s role as a containerization platform is critical in establishing a consistent, efficient workflow. By focusing on Docker, you learn to compile, run, and manage Node.js code with containers, ensuring that the development environment is replicable and consistent across any platform.

Practical Application in Course Projects: While not mandatory, the skills acquired in this session can greatly benefit you in the course-related projects and assignments, especially if they choose to utilize Node.js and Docker.

Leveraging Seamless Integration with Development Tools: Docker’s compatibility with a range of development tools, like Nodemon for Node.js, exemplifies its role in streamlining the development process. These tools automate and simplify tasks, enhancing the overall efficiency of developing, testing, and debugging Node.js applications.

Benefiting from Vast Community Support: Both Docker and Node.js are supported by robust online communities. This vast network offers an abundance of resources, guidance, and shared knowledge, which you can leverage for troubleshooting, learning best practices, and keeping up-to-date with the latest advancements in web development.

Generated with the help of ChatGPT

Getting Ready

Ensure that you have Docker set up and running in your system.

Here are some common terminologies used in Docker that you should familiarise yourselves with:

Term Description
Docker Daemon Listens to Docker API requests. Manages Docker objects - images, containers, networks and volumes. It can also communicate with other daemons.
Docker Image Read-only templates used to create Docker containers. You can create your own image or use pre-existing ones.
Docker Container A runnable instance of an image. You can create, start, stop, move, or delete a container using the Docker API or CLI.
Docker Registry A repository for Docker images. Docker Hub is the default registry. Using docker pull or docker run commands uses the required images from the configured registry.
Docker Client The primary way users interact with Docker. It sends commands to the Docker Daemon. It can communicate with >1 daemon.
Docker Desktop A GUI tool that includes the Docker Daemon, Client, Docker Compose, Content Trust, Kubernetes, etc.
Docker Objects Images, containers, networks, volumes, plugins, etc.

Referenced from SDS SE Toolbox - Containerization

Dockerfile

What is a Dockerfile?

Key Instructions

Instruction Usage
FROM Specifies the base image to start building your image. For example, FROM ubuntu:18.04 starts with the Ubuntu 18.04 image.
RUN Executes a command and commits the results. Used for installing software packages, for example.
COPY and ADD Both are used to copy files from the host filesystem to the container. COPY is straightforward, while ADD has some extra features like remote URL support and tar extraction.
CMD Provides a command and its default arguments that will be executed when the container starts. Only the last CMD instruction is effective.
ENTRYPOINT Similar to CMD, but is meant to define the container’s main executable and its arguments are appended to the entrypoint.
ENV Sets environment variables.
EXPOSE Indicates which ports the container listens on.
WORKDIR Sets the working directory for any RUN, CMD, ENTRYPOINT, COPY, and ADD instructions.

A part of this table was generated with the help of ChatGPT

Basic Structure

Sample Dockerfile

# Use an official Node.js runtime as a parent image
FROM node:24-alpine

# Set the working directory in the container
WORKDIR /app

# Copy package.json and package-lock.json
COPY package*.json ./

# Install any dependencies
RUN npm install

# Bundle app source inside the Docker image
COPY . .

# Make port 3000 available to the world outside this container
EXPOSE 3000

# Define the command to run your app
CMD ["node", "app.js"]

Best Practices

Docker Commands

Node.js Application

Getting Ready

The Dockerfile

The Dockerfile is currently empty. The aim of this hands-on is to teach you how to write a Dockerfile.

  1. As we are interested to build a Node.js Application using React, we need to have a runtime environment that has our desired version of Node.js installed. Thus, we would use the official Node image as a parent image to acheieve our objective.

    Add the following line to the Dockerfile:

     FROM node:24-alpine
    
  2. Next, we specify the working directory in the container.

    Add the following line to the Dockerfile:

     WORKDIR /app
    
  3. In the case of Node.js applications, we need to copy the package.json and package-lock.json (or yarn.lock) files as these include the relevant dependencies of our app. Node.js relies on these files to lookup and install the dependencies.

    Add the following line to the Dockerfile:

     COPY package*.json ./
    
  4. After the files have been copied, the relevant dependencies have to be installed, and on using the command npm install, the required node modules (dependencies) are installed.

    Add the following line to the Dockerfile:

     RUN npm install
    
  5. After all dependencies have been installed, we copy the entire source code directory into the working directory of the container and this includes all of the relevant code.

    Add the following line to the Dockerfile:

     COPY . .
    
  6. Next, we want to be able to interact with our app and as React based apps by default start on port 3000, we expose that port. This allows the outside world to interact with the created container and our app using port 3000.

    Add the following line to the Dockerfile:

     EXPOSE 3000
    
  7. Now that all is set up, we want to start our app. For starting React based apps, the command used is npm start, and hence that is what we add as a command next.

    Add the following line to the Dockerfile:

     CMD ["npm", "start"]
    

Finally, the created Dockerfile should look like this:

FROM node:24-alpine

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 3000

CMD ["npm", "start"]

Building the Image

Now that the Dockerfile is setup, we have a skeleton for the image of our app.

Using the docker build command, we will now create an image of our app.

  1. Open a command line/terminal window and navigate to the downloaded/cloned repo, and then into the demo-app directory where the Dockerfile is located.

  2. Run the following command:

     docker build -t docker-demo-app .
    
    • Above, the -t flag tags the create image with the name docker-demo-app.
    • . refers to look for the Dockerfile in the Current Working Directory (CWD)

Running the Container

With the Image ready, we can run our React app using Docker.

  1. In the same command line/terminal window, run the following command:

     docker run --name my-app -p 3000:3000 -d docker-demo-app
    
    • Above, the flag -p allows to bind our systems port 3000 to the port 3000 of container.
    • The flag -d allows to run the Docker Container in detached mode.
  2. Open your browser of choice and go to http://localhost:3000/ to interact with the React App.

  3. The running container can be stopped and removed using the command:

     docker stop my-app
     docker rm my-app
    

Binding the Current Working Directory

Binding the Current Working Directory with the Docker Container allows you to edit your code on your local system and see the changes in the app in real-time that is running on the Docker Container.

Note: Ensure you have the folder node_modules with all the necessary dependencies in your local system. If not, run npm install locally.

  1. In the same command line/terminal window, run the following command:

    macOS users:

     docker run --name my-app -p 3000:3000 -v "$(pwd):/app" -d docker-demo-app
    

    Windows Command Line users:

     docker run --name my-app -p 3000:3000 -v "%cd%:/app" -d docker-demo-app
    

    Windows Powershell users:

     docker run --name my-app -p 3000:3000 -v ${PWD}:/app -d docker-demo-app
    
    • The flag -v allows to mount the Current Working Directory as a volume in the Docker Container.
    • Note for Windows user: Mounting volume using a window host may slow down the application. For better experience, consider using WSL2. For more details, see this Stack Overflow post. Remember, while mounting volume improves developer experience, it is not essential for development.
  2. Go to src/App.js and add the following code inside the function App():

     function toggleAnimation() {
       var logo = document.querySelector(".App-logo-clockwise");
       var isLogoRotatingClockwise = logo !== null;
    
       if (isLogoRotatingClockwise) {
         logo.classList.remove("App-logo-clockwise");
         logo.classList.add("App-logo-anti-clockwise");
       } else {
         logo = document.querySelector(".App-logo-anti-clockwise");
    
         logo.classList.remove("App-logo-anti-clockwise");
         logo.classList.add("App-logo-clockwise");
       }
     }
    
  3. In the same file, add the following code inside the <header> tag, right after the <p> tag

     <button className="spin-btn" onClick={() => toggleAnimation()}>
       Toggle Spin Direction
     </button>
    
  4. Press CTRL/CMD + S

  5. You should now be able to see a button that says “Toggle Spin Direction”, which on clicking will change the spin direction of the React logo.

    Updated React Page text

  6. The running container can be stopped and removed using the command:

     docker stop my-app
     docker rm my-app
    

    :warning: Note: If you do not bind volumes, such changes can not be seen in real-time, and the containers have to be stopped and run again, making the process tedious. Please refer to the section on Binding the Current Working Directory

Exercise

  1. Create a Dockerfile for demo-service based on the following inputs:

    • Parent image is node:24-alpine
    • Working directory as /app
    • Service Port Number 3001
    • Command to run service: npm start
  2. Build and Run the container.

  3. Go to http://localhost:3001/ and you should see the following output:

    Jokes API Page

  4. Once completed, stop and remove the container.

Docker Compose

Overview of Docker Compose

Docker Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services, which simplifies the process of managing and deploying multi-container applications.

Generated with the help of ChatGPT

Key Concepts

Generated with the help of ChatGPT

Sample Docker Compose file

services:
  web:  # the name of the first service
    image: nginx:latest  # uses the latest Nginx image
    ports:
      - "80:80"  # maps port 80 of the container to port 80 on the host
    volumes:
      - ./html:/usr/share/nginx/html  # mounts the 'html' directory from the host to the container
    networks:
      - webnet  # links this service to the network named 'webnet'

  db:  # the name of the second service
    image: postgres:latest  # uses the latest PostgreSQL image
    environment:
      POSTGRES_DB: mydatabase  # sets the PostgreSQL database name
      POSTGRES_USER: user  # sets the PostgreSQL user
      POSTGRES_PASSWORD: password  # sets the PostgreSQL password
    volumes:
      - db-data:/var/lib/postgresql/data  # mounts the 'db-data' volume to the container
    networks:
      - webnet  # links this service to the network named 'webnet'

volumes:
  db-data:  # declares a volume named 'db-data' for persistent data storage

networks:
  webnet:  # declares a user-defined network named 'webnet'

Docker Compose Commands

Node.js Application

Building the Images

Open a command line/terminal window and navigate to the root directory of the DockerHandsOnTutorial repository that you’ve downloaded or cloned. This directory contains the docker-compose.yml file.

Execute the command below to build all the services, networks, and volumes defined in your docker-compose.yml file. This process is efficient as it doesn’t necessitate running individual build commands for each service.

docker-compose build --no-cache

Running the Containers

  1. By executing the following command, all the services defined in the docker-compose.yml file are ran.

     docker-compose up -d
    
    • Above, the flag -d runs all containers in detached mode.
  2. Open your browser of choice and go to http://localhost:3000/ to interact with the React App.

  3. The running container can be stopped using the command:

     docker-compose down
    

Exercise

  1. Edit the docker-compose.yml file and add demo-service as a service in the file.

  2. Build and Run the containers using docker-compose commands.

  3. Go to http://localhost:3000/jokes and you should see the following output:

    Jokes Page

  4. Feel free to add and edit code to see changes happen in real-time.

Conclusion

As we conclude this tutorial, it’s clear that integrating Docker into the Node.js development process brings substantial benefits, streamlining and refining how we build, run, and manage applications. Key highlights include:

Generated with the help of ChatGPT

Where to go from here?

You may learn more about the following with plenty of resources available all over the internet:

References

AI Declaration

Some parts of this guide were structured, formatted, and refined with the assistance of ChatGPT. The model was used to draft technical explanations and generate code snippets. All code snippets used in the guide and command sequences were reviewed, implemented, and tested by the teaching team to ensure accuracy and functionality.