In this post we will see simple steps to get started with setting up a nodejs app in docker.
Step1 - Basic Setup
First step is install docker, for this the official guide can be followed https://docs.docker.com/engine/install/ubuntu/
Next step is to have a running express app. For the purpose of this example let’s use the app from express official website https://expressjs.com/en/starter/hello-world.html
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => res.send('Hello World!'))
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))
So you can follow this steps on terminal to get this app up and running.
So these steps can be followed to get a basic app running
mkdir sample_app
cd sample_app
nano index.js # copy paste the above code here
npm init # just keep pressing enter
npm install express --save
node index.js
After doing above you should have a app running open localhost:3000 on your browser to confirm.
Step2 - Dockerfile
We have installed docker and also have expressjs app running. Now let’s try to run the same app using docker.
First we need to create a Dockerfile.
Dockerfile has instruction which we provide to docker to run the app.
Create a new file called “Dockerfile” in project root folder and paste this
FROM node:13
WORKDIR /workspace
COPY package.json /workspace
COPY index.js /workspace
RUN npm install
EXPOSE 3000
CMD [ "node" , "index.js" ]
The above code should be self explanatory. If any instruction is not clear, there is documentation available online.
Next run the command
sudo docker build -t sample_blog_image .
What we done here is just build the above docker file and “-t” is used to tag the build with name “sample_blog”.
If all went well you should see output like this
Removing intermediate container 576a205ac8f0
---> ea35b2c0c8ca
Step 6/7 : EXPOSE 3000
---> Running in 171adcd7515e
Removing intermediate container 171adcd7515e
---> 2bfa2fadcdc9
Step 7/7 : CMD [ "node" , "index.js" ]
---> Running in 9ec883e0d78d
Removing intermediate container 9ec883e0d78d
---> 4792f9f8377a
Successfully built 4792f9f8377a
Successfully tagged sample_blog:latest
Take a step back here and lets see what we have.
Review Steps
What we did till now. First we instruction to use node:13
Docker run on images, image are basically recompiled operating systems which are devoted to one specific task. Mostly these are based on alpine linux https://alpinelinux.org/about/ and maintained here https://hub.docker.com/
When we mentioned node:13 it refers to the official image here https://hub.docker.com/_/node
So in layman terms, docker will download this linux image with node 13 installed on it and run it on your system. Cool!
Next, we do WORKDIR /workspace
this basically creates a directly called “workspace” in the above image and sets that as the active directly to run our commands.
Next, we have COPY commands which basically copies files from our local server or system to the linux image.
Next, using the RUN operation we can run commands on the linux image i.e npm install
Next, we EXPORT port 3000. by default all ports in a docker image a closed, expose opens them up.
Next, we have instruction called “CMD”. This is a special instruction (similar to ENTRYPOINT).
So basically will keep running the container, as long as the program in CMD is running. If the program exists, docker will also exit the container. We will see this later in practice.
Step3 - Run
Till now, we have build out image using the command
sudo docker build -t sample_blog_image .
To confirm you can run
sudo docker image ls
Next to run this image.
sudo docker container run --name sample_blog_container -p 5000:3000 sample_blog_image
This will run our image “sample_blog_image” with a container name “sample_blog_container”. Also this will map the port 3000 of the image to port 5000 in our system.
If this works properly you will see an output like this
Run localhost:5000 and you will see Hello Word again.
There are few important things to notice here
If you do CTRL + C or CTRL + D to stop the docker process it won’t work.
So to stop the process, open another terminal and run the command
sudo docker rm sample_blog_container --force
This will stop the container.
There are few more command to which are useful. If you want to run docker in background run this command with -d i.e daemon mode
sudo docker container run --name sample_blog_container -d -p 5000:3000 sample_blog_image
If you want to open terminal of the linux os i.e the nodejs image run this command
sudo docker container run -it --rm -p 5000:3000 sample_blog_image bash
basically this run a container and open bash on it
If you already have a running container in daemon and you want to login into that use the command
sudo docker exec -it sample_blog_container bash
if you run the “top” command inside the container you will see your node script already running
Developing and Debugging
Till now, we deployed a simple expressjs app on nodejs. But this is not enough, we need to keep developer the app an debugging this. As the app gets more complex, we should be easily able to update code, see logs as if we are working without docker!
Let’s see how to do it.
Quick tip. Run command sudo docker container ls or sudo docker image ls to see your existing containers
Docker has concept of volumes https://docs.docker.com/storage/volumes/
Using docker volume’s we are able to link our system’s hard disk persistent storage to dockers container storage. So any change we make in our hard disk will get instantly reflected in the docker container and visa versa.
So to update our source code inside docker container, we need to setup a volume. Let’s see how to do it.
First, lets move our index.js file inside a directory called “src” so its src/index.js
Next, we change this in our Dockerfile
COPY package.json /workspace
#COPY index.js /workspace
VOLUME [ "/workspace/src" ]
So we made /workspace/src a volume
also change the CMD to “CMD [ “node” , “src/index.js” ]”
so our final Dockerfile looks liek
FROM node:13
WORKDIR /workspace
COPY package.json /workspace
#COPY index.js /workspace
VOLUME [ "/workspace/src" ]
RUN npm install
EXPOSE 3000
CMD [ "node" , "src/index.js" ]
Next, build your image again
sudo docker build -t sample_blog_image .
To run the container
sudo docker container run --name sample_blog_container -v $(pwd)/src:/workspace/src -d -p 5000:3000 sample_blog_image
Here we are mapping the src/ folder in our pwd to the volume, both get linked.
You can confirm the same. Change some index in src/index.js in our system. Then using the command
sudo docker exec -it sample_blog_container bash
cat src/index.js
You can verify that the file automatically gets updated.
For further ease of development, we can use tools like nodemon to automatically react to file changes.
Let’s install nodemon in our Dockerfile (P.S. Do note this can be done via package.json also)
FROM node:13
WORKDIR /workspace
COPY package.json /workspace
#COPY index.js /workspace
VOLUME [ "/workspace/src" ]
RUN npm install
RUN npm install -g nodemon
EXPOSE 3000
CMD [ "nodemon" ,"src/index.js" ]
also in your package.json change
"main": "src/index.js",
Next,
sudo docker build -t sample_blog_image .
sudo docker container run --name sample_blog_container -v $(pwd)/src:/workspace/src -p 5000:3000 sample_blog_image
Now, if you edit index.js via a code editor you will automatically see nodemon running inside
TIP: you can have different dockerfile to production vs development
In this post we saw basics of docker and how to run a app with it.