Containerizing a Node.js app using Docker


Objectives

Learn how to containerize an application and share it on Dockerhub.

Docker Image vs. Container

I found a great explanation in CircleCI Blog: A Docker image executes code in a Docker container. You add a writable layer of core functionalities on a Docker image to create a running container. Think of a Docker container as a running image instance. You can create many containers from the same image, each with its own unique data and state.

Get started

Clone the start project provided by Docker

git clone https://github.com/docker/getting-started.git

Build the image

I will not walk through this full-stack node.js project. Just keep in mind that it is a full-stack node.js project including a frontend and a backend.

Create Dockerfile

In the app directory, the same location as the package.json file, create a file named Dockerfile.

Let's walk through the Dockerfile step by step:

  • FROM node:18-alpine: Specifies the base image for the Docker container. It uses the official node image with the tag 18-alpine, which is a lightweight version based on Alpine Linux. This image provides a pre-configured environment for running Node.js applications. Checkout the spec on Docker Hub.
  • WORKDIR /app: Sets the working directory inside the container to /app. It means that any subsequent commands will be executed relative to this directory.
  • COPY . .: Copies the contents of the current directory (where the Dockerfile is located) into the /app directory of the container. The first . represents the source directory on the host machine, and the second . represents the destination directory in the container (in this case, /app).
  • RUN yarn install --production: Runs the command yarn install --production inside the container during the image build process. It installs the dependencies specified in the package.json file of the application. The --production flag ensures that only production dependencies are installed, excluding any development dependencies.
  • CMD ["node", "src/index.js"]: Sets the default command to run when the container is started. It specifies that the node command should be executed with the argument src/index.js. This command launches the Node.js application, assuming that the entry point is located at src/index.js.
  • EXPOSE 3000: Exposes port 3000 from the container to the host machine. It doesn't actually publish the port, but rather serves as documentation for users or developers to know which port the application inside the container is expected to listen on.

Build the image

docker build -t getting-started .

The docker build command uses the Dockerfile to build a new container image. And the -t flag tags your image. Think of this simply as a human-readable name for the final image. Since you named the image getting-started, you can refer to that image when you run a container.

Observation

During the build process, you might have observed that Docker fetched multiple "layers". These layers are a result of the instruction in the Dockerfile specifying the use of the node:18-alpine image as the base. Since the node:18-alpine image was not present on your local machine, Docker had to download it from a remote repository.

Start an app container

Now that you have an image, you can run the application in a container. To do so, you will use the docker run command.

docker run -dp 127.0.0.1:3000:3000 getting-started
          

Open your web browser to http://localhost:3000. You should see your app.

Update the application

If you make changes to the code of your application and want to run a new container with the updated code, it's recommended to stop and remove the old container before starting a new one. This ensures that you have a clean and up-to-date environment for your application.

Remove old container

docker stop YOUR_CONTAINER_ID // Use the docker stop command to stop the container. 
docker rm YOUR_CONTAINER_ID // Once the container has stopped, you can remove it by using the docker rm command.
          

Start the updated container and run:

docker run -dp 127.0.0.1:3000:3000 getting-started
          

Share the application

  • Create a repo on docker Hub
  • Name the repo: getting-started-test
  • Login the docker hub
  • Use the docker tag command to give the getting-started image a new name.
  • docker tag getting-started YOUR-USER-NAME/getting-started-test
  • Push the application to the remote repo
  • docker push YOUR-USER-NAME/getting-started-test

You should see your application on docker hub now:

Conclusion

Building a Docker image is a quick and easy process that offers numerous benefits, such as:

  • Portability: Docker images can run consistently across different environments, making it easier to deploy and scale applications.
  • Isolation: Each Docker container operates in its own isolated environment, ensuring that applications and their dependencies remain separate and do not interfere with each other.

Source