Containerising a Node Express App

If you need an example application to use try this Simple JSON API

In this basic example of how to containerize a node express app we will use Docker. It is assumed that that you have a working express application that you are ready to put into a container for deployment purposes.

Also assumed is that you have access to the Docker CLI which you can verify by running the following command in your terminal:

docker -v

# Docker version 19.03.5, build 633a0ea (your version may differ)

Building A Container

Navigate to your project folder and create a Dockerfile

Note: This file is case sensitive so use a capital 'D'

# Use apline linux where ever possible
# to keep container sizes to a minumum
FROM node:alpine

# Application Code
WORKDIR /app

# Install app dependencies
COPY package*.json ./

# Install Dependancies
RUN npm install

# Expose port 3030 from the container
EXPOSE 3030

# Run server by default
CMD ["npm", "start"]

# Add application code
COPY . .

Each command in the Dockerfile results in a layer that is added to the base image layers. It is worth thinking about the order of operation here. Most updates that you make to your app after having deployed a build for testing will be code changes. As you can by the order of the commands copying in the application code is the last step. So if the only change is something in your application code only the last layer would need to be re-build in subsequent builds. If you were to add a new package to your project, then all the layers down to COPY package*.json ./ would need to be discarded and rebuilt.

The most important part of this file is the EXPOSE port number, in my example app it listens on port 3000, yours may be different, change the port number to suit if required.

If you are using the Simple JSON API example, this is all tha should be required to build your container. Using the docker CLI we can initiate a build using the docker build command:

docker build -t example-json-api:dev .

In this command the -t stands for the tag name and the . is just referring to the current directory as the location of the Dockerfile

Once the build is completed you should see the last entry on the terminal indicating success:

# Successfully tagged example-json-api:dev

Now you should be able to see this new image in your list of local images:

docker images
# REPOSITORY        TAG  IMAGE ID      CREATED        SIZE
# example-json-api  dev  6ba575ffb51f  2 minutes ago  83.2MB

Running A Container

You can test run the image with the following command:

docker run --rm -p 3000:3000 example-json-api:dev

The --rm flag here will automatically remove the container from the local container instances once you <ctrl> + C to stop the running container.

The -p flag tells docker to map local ports to the exposed container ports <local port>:<container port>

Then you should be able to interact your app on the port that is exposed. In this example by opening a new termainal and using curl. The running container should log to the console when new requests are recieved.

# [2020-04-25T16:01:51.034Z] Example JSON API listening at http://localhost:3000
# [2020-04-25T16:01:54.271Z] POST /echo -> {"message":"This is my new JSON API"}
# [2020-04-25T16:01:55.284Z] GET /

Stop the container by using <ctrl> + C in the running container terminal.

Saving A Container

Docker CLI has a very useful save command that we can use to convert the container into a .tar.gz file which can then be shared.

docker save example-json-api:dev | gzip > example-json-api.tar.gz

Alternitively if you prefer not to gzip the result you will end up with a .tar file

docker save example-json-api:dev > example-json-api.tar

Delete A Container

Using Docker CLI you can remove a container image as long as it's not currently running on in a container instance with the following command:

docker rmi example-json-api:dev

You can check that it's successfully removed from your local images by running this command and verifying that it's not in the list:

docker images

Loading A Container

Docker CLI also has a load command for extracting .tar or .tar.gz archives that you may have saved.

docker load --input example-json-api.tar.gz

Or if you have a .tar file

docker load --input example-json-api.tar

You can check that it's successfully loaded into your local images by running this command and verifying that it is in the list:

docker images
# REPOSITORY        TAG  IMAGE ID      CREATED        SIZE
# example-json-api  dev  6ba575ffb51f  5 minutes ago  83.2MB

The beauty of containerising your application is that it will be able to run on any platform that docker can run on, and there will not be any cross platform issues that mean there are subtle differences in how your app runs or unexpected bugs based on different underlying hardware.