In this tutorial, we will discuss the steps on how you can properly run a Docker container for your local development. I will be using an Express server running on NodeJS as an example. We will configure the Docker container to update and restart the server inside the container whenever there is a change in the code.
If you are new to Docker, you may want to read my article "Docker in 1 minute" to quickly get an understanding of Docker.
Initialising a sample express application
For this example, we will be using a simple Express server on nodeJS as our application. This will allow us to know whether our Docker setup is configured correctly or not.
1. Installing packages
First, we will need to install "Express" and "nodemon". "Express" is a popular nodeJS server application in the javascript community. "nodemon" is used to automatically restart the server whenever there is a change in any files and for this reason, it is commonly used in local development.
2. Configuring the package.json file
Open the package.json
file and copy-paste the above JSON data.
Keep thetype
andscripts
configuration as shown in the abovepackage.json
file for the purposes of this example. You may change the remaining if you know what you are doing.
3. Setting up our express server
Create a file named server.js
and copy the above code in it. We have our simple express server ready to run.
4. Run our Express server
If you have done everything correctly so far, you will have a running server with the following output in the console.
Setting up Docker for local development
Now, we are ready to create a docker container from which we will run our server.
Here are the steps involved: First, we need to create a docker image from a "Dockerfile". Second, we will spin up a docker container from our docker image. Finally, we will create a "docker-compose.yaml" file specifically for our local development purpose. We will go through each step one by one.
1. Creating a Dockerfile
In your code editor, create a file named Dockerfile
without any filename extension and copy the above code into the new file
A Dockerfile
is a set of instructions for creating a Docker image ( A standalone environment which can be replicated easily).
In the above Dockerfile
, we are using a node image of version 16. We then defined a new default working directory called "app" for all the subsequent operations to be performed. Next, we copy the "package.json" and "package-lock.json" files to our "app" directory and run the install packages command.
We are installing "nodemon" seperately as a global package using the tag "-g". This is done for the local development purpose, which comes later on when configuring the docker-compose.yaml
file.
Next, we copy all the files from our development folder to the docker image. Once all the above tasks are completed, Docker will run the node server.js
command to start the server.
2. Create a .dockerignore file
If you have used "git", you may already be familiar with what this does. The .dockerignore
file tells Docker to avoid certain folders or files from being considered when running operations.
Create a file named.dockerignore
and paste the above code into it. Here, we are ignoring the "node_modules" folder containing all the installed packages. This is because we will be installing all the packages inside the Docker image itself.
We could build the contanier right now and we will be having a running server using Docker. But, our goal to enable this container as a local debugging server. So that any time we make a change in the code, the server running inside the container automatically restart to reflect the changes. This is where docker-compose.yaml
comes in.
3. Creating a docker-compose.yaml file
Normally in the local development of a NodeJS application, we will be using a package called "nodemon" to automatically restart the server whenever there is a change in the code. The following command will enable the auto-restart on code change
You might have noticed that we didn't use "nodemon" to run the server inside the Dockerfile
command, because in most cases Dockerfile
is used as a base configuration for running an actual production server. A production server doesn't require the restarting facility like in development as we are not making any changes to the code frequently. This is where thedocker-compose.yaml
file comes into play.
Create a file named docker-compose.yaml
and copy the above code in it.
docker-compose.yaml
contains a collection of services that can be spun in a single docker-compose build command. In our case, we are only using a single service with the name "app".
The instructions in docker-compose will overwrite the Dockerfile
instruction, if the same exists.
In this case, we have written a new "command" nodemon server.js localhost 80
which will replace the "command" node server.js
in the Dockerfile
. Then, we made a port mapping from port 80 (inside the docker container) to port 5000 (actual port in our system) and also mapped the "/app" directory (inside the docker container) to "." (actual project directory in our system).
Now, let's run the following docker-compose command to build and run our container:
docker-compose up --build
In the above command, the "up" tag enables the syncing of the files and folders between the container and the developemnt directory.
If you have done everything correctly, you will have the following logs in the console.
If you are using Docker Desktop, you will notice something similar in the "Containers/Apps" section as below.
4. Testing whether the code is synced
If you open the link localhost:3000
in your web browser, you should be geting the message "Server is running!".
Let's make a change to this message in our local code and see if the server gets updated inside the Docker. Go to the server.js
file and make the following change to the response message.
app.get("/", (req, res) => {
res.send("Server is running! I have edited this.");
});
Now, if you go to your browser and refresh the page (localhost:3000
), you will get the updated response from the server.
Voila!! We have our local development server running inside Docker.
Bonus Tip - Setting up Docker for production
Since we have already configured a proper Dockerfile
for production where we are running the command node server.js
. We can now, straight up use the Docker build command to run a production server.
Run the following Docker command to run a production server:
docker build -t hello-world-api .
The "-t" tag is used to attach a reference name to the Docker container. And the "." in the end represent the build diretory inside the container.
Conclusion
We have built a simple Express server and have set up Docker for local development and debugging with the functionality of "nodemon". We also configured the Dockerfile
to run a production server using the docker build command.
If you found this useful, you will find other interesting articles on my blog. See you in the next one.