A modern approach to developing Node.js Applications with Docker
Often, developers around the world spend their precious time managing different node versions on their local machine. Though NVM (Node Version Manager) is a good bet, it still doesn’t provide you a fresh and clean environment for all your projects on the same machine. So, Docker, a popular container runtime engine, comes to the rescue here.
Introduction to Docker Containers
In this article, we will discuss a modern approach to developing Node.js applications using Docker containers. Docker is great in providing an isolated and clean environment for simultaneous development on single or multiple projects. On top of that, it gives us the ability to create images based on code versioning. Nevertheless, the most important feature required for a developer is “hot reloading” while developing an application. “Hot reloading”, which only refreshes the edited files during runtime and maintains the current state of an application, is essential for local development and we can achieve it with Docker.
Traditional Approach with Docker Container
Before we proceed further with the modern approach, we can discuss the traditional way of doing things with Docker. The traditional way of packaging and running the code in Docker containers involve creating a Docker image based on a Dockerfile. Creating a docker image makes sense, as we try to set up the production environment in the most tailored manner. We try to include all the dependencies – binaries, libraries etc. – which would essentially help our app code run flawlessly.
Having said that, it becomes a requirement to write a Dockerfile, create an image every time you modify the code and then, run it via docker run command or docker-compose. It is like a multi-step, tedious process that a developer must go through before they could spin up a magic container with their code running inside it. So, when hard deadlines approach, this could very well become a bottleneck due to the time it would take to complete the overall build and run process.
Let’s bring on some magic now. We are now going to discuss a very cool, new approach to Docker development.
Modern Approach with Docker Container
The key is, we don’t always need a Dockerfile to compile, build and run our code. With easy tweaks in the docker-compose.yml file, we can do away with the Dockerfile and iterative Docker image creation steps.
Let us look at a sample docker-compose.yml file which is in accordance with this approach.
version: "3.7" services: backend: image: "node:11" user: "node" working_dir: /home/node/app environment: - NODE_ENV=development volumes: - ./:/home/node/app ports: - 8080:8080 command: > /bin/bash -c " npm install; npm start; "
In this docker-compose.yml, we have used an official node image which is available on Docker Hub – https://hub.docker.com/_/node/. You can customize the node image and version as per your requirements.
Key points in Customizing the Node Image
Now, we have to meticulously observe a few things over here:
1. We are binding our current directory (where the code resides) with the directory inside the container as a Docker volume. So, we don’t need to copy any code inside the container.
2. This also implies that any change in any of the files in the current directory will impact the running code and it will be reflected, as the code is running directly from here. So, hot reloading will happen for sure. We have tried it and it works like charm.
3. Now, you must be wondering how we will deal with npm commands like npm install, npm run, etc. which are required before starting up the application. For that, we are going to use the below-mentioned syntax, which would run the commands in sequential order. You can modify these commands based on your requirements.
command: > /bin/bash -c " npm install; npm start; "
4. That’s it. Now, we just need to start the process by running the docker-compose command.
5. After the above command finishes execution, your services would be up and running. Now, any change in any of the code files inside the directory which is mounted with the specified container would be reflected. Voila! Hot reloading would work as it does without the Docker setup.
6. So, with this approach, you would be able to create the development setup using Docker on your local machine, easily.
A couple of helpful notes:
1. Though we have accomplished the task of executing the npm run commands as discussed above, there are other ways too which may be deemed fit for larger projects.
Another method suggested is to use docker compose’s “extends” keyword to do the same task. With this we can share the project’s configuration files among different services and even with different projects. We can use extend keyword in combination with node modules directory as a shared Docker volume on all the services. It’s worth trying and cleaner than the bash method for running the docker CMD commands.
Having said that, we should also remember that any modification with respect to mounting of node modules directory should be done with extreme care, otherwise the process may break.
2. You can create a “makefile” to maintain your long Docker commands. It is really handy and helps in the long run.
Hope we were able to explain a few better ways of developing Node.js applications using Docker and docker-compose. Although this article revolves around Node.js, the underlying concepts can be extended to any language by incorporating necessary changes. Hope you found this article useful, more on this would be explained in the upcoming articles.