How to Dockerize a Node app and deploy to Heroku
Tags: docker, container, node, heroku
Published: 2020-03-21 23:09:38

---
title: How to Dockerize a Node app and deploy to Heroku
published: true
description: A small tutorial on how to dockerize an existing node app and deploy it to Heroku.
tags: docker, container, node, Heroku
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/68f7yq81c8e049bxvav6.png
---

I had previously made a tutorial about [how to create a Fullstack app](https://dev.to/pacheco/my-fullstack-setup-node-js-react-js-and-mongodb-2a4k) and now I'm going to show how to dockerize this app and deploy it to Heroku.
If you didn't follow the previous tutorial, I recommend you to fork the repo and play around with the app.

You can fork the [Fullstack app repo here](https://github.com/thisk8brd/node-react-starter).

# Content
- [Production environment](#prod-env)
   - [Dockerfile](#prod-env-dockerfile)
   - [package.json start script](#prod-env-script)
   - [docker-compose file](#prod-env-docker-compose)
   - [.dockerignore file](#dockerignore)
   - [Run production test](#prod-env-run)
- [Development environment](#dev-env)
   - [Dockerfile](#dev-env-dockerfile)
   - [Complete dockerfile](#dev-env-complete-dockerfile)
   - [docker-compose file](#dev-env-docker-compose)
   - [Run development](#dev-env-run)
- [Heroku deployment](#deploy)
   - [Heroku registry login](#deploy-login)
   - [Create a heroku app](#deploy-create)
   - [Set image TAG](#deploy-image-tag)
   - [Push image to heroku registry](#deploy-image-push)
   - [Release the image](#deploy-image-release)
- [Example app](https://hidden-springs-14394.herokuapp.com/)
- [Repo](https://github.com/thisk8brd/node-react-starter/tree/docker)


So let's dive into code!

![](https://media.giphy.com/media/3ohhwABuFKvlqx4u4w/giphy.gif)

Clone the [repo](https://github.com/thisk8brd/node-react-starter).

Download all the dependencies

```
$ npm install && cd client && yarn
```

# Production environment <a name="prod-env"></a>

We start with the production environment because we are going to create a multistage Dockerfile, and the development environment can simply copy the production environment adding the necessary extra configuration.

### First step: Dockerfile <a name="prod-env-dockerfile"></a>

In the root of your project, create a Dockerfile with the following content:

```yml
#Dockerfile
# PROD CONFIG
FROM node as prod

WORKDIR /app

COPY package*.json ./

RUN npm install

WORKDIR /app/client

COPY ./client/package*.json ./

RUN npm install

WORKDIR /app

COPY . .

ENV NODE_ENV=production

CMD [ "npm", "start" ]
```

>The first line, we define which image we are going to use and an alias, in this case, a <strong>node</strong> image and a <strong>prod</strong> alias.

>WORKDIR: Workdir works as mkdir (Create a folder) and cd /folder (enter into the folder).

>COPY and RUN: In these lines 6 and 8, we copy the package.json and package-lock.json into our workdir and install the dependencies.

>Lines 10, 12 and 14: Here we change the workdir, repeat the copy and run actions, but this time we install the dependencies for the client (frontend app).

>Lines 16 and 18 we go back to the project root dir and copy all the files. We just copy all the files if the previous steps succeeded.

>Lines 20 and 22: Here we define the environment as production and run the start script.

### 2 step: package.json prod config <a name="prod-env-script"></a>

To run the production config, we need to build the frontend app and we can do that by adding a pre-start script.
Open the package.json file in the root of the project and add the following script:

```json
"prestart": "npm run build --prefix client",
```

### 3 step: docker-compose production file <a name="prod-env-docker-compose"></a>

Now we are already able to build the image and run it, and the best way to do it is by using a docker-compose file.
In the root of the project, create a docker-compose-test-prod.yml with the following content:

```yml
version: "3.7"

services:

  node-react-starter-db:
    image: mongo
    restart: always
    container_name: node-react-starter-db
    ports:
      - 27017:27017 
    volumes:
      - node-react-starter-db:/data/db
    networks: 
      - node-react-starter-network

  node-react-starter-app:
    image: thisk8brd/node-react-starter-app:prod
    build: 
      context: .
      target: prod
    container_name: node-react-starter-app
    restart: always
    volumes: 
      - .:/app
    ports:
      - "80:5000"
    depends_on:
      - node-react-starter-db
    environment:
      - MONGODB_URI=mongodb://node-react-starter-db/node-react-starter-db
    networks: 
      - node-react-starter-network

volumes: 
  node-react-starter-db:
    name: node-react-starter-db

networks: 
  node-react-starter-network:
    name: node-react-starter-network
```

>As this project uses mongodb, the first service node-react-starter-db runs a mongoDB container using the network and volumes that will be created at the end of the file.

>Our app is defined in the second service, in which we define the build file and the target.
> <strong>context: .</strong> means it tries to search for a Dockerfile in the root of the project.
>the <strong>target </strong> tag defines which stage we are going to use, in this case prod.

<a name="dockerignore"></a>
### Create a .dockerignore file in the root of the project with the following content:

```
.git/
node_modules/
client/node_modules/
npm-debug
docker-compose*.yml
```

<a name="prod-env-run"></a>
## Run production test environment

![](https://media.giphy.com/media/3gYUXn5rciQQDv8mmp/source.gif)

At this point, we can already test a production environment and we can do it by running the following command in the root of your project:

```
docker-compose -f docker-compose-test-prod.yml up 
```

Now if we visit http://localhost we can see the following screen:

![](https://thepracticaldev.s3.amazonaws.com/i/xfa4yz3gbmlg4r9s3qh9.png)

Use a HTTP client like [Postman](https://www.getpostman.com/) or [Insomnia](https://insomnia.rest/) to add some products. Make a POST request to http://localhost/api/product with the following JSON content:

```json
{
  "name": "<product name>",
  "description": "<product description here>"
}
```

Now, you will be able to see a list of products rendered on the screen, like so:

![](https://thepracticaldev.s3.amazonaws.com/i/8n282icdca6e01esu7eb.png)

<a name="dev-env"></a>
# Development environment

<a name="dev-env-dockerfile"></a>
Let's update our Dockerfile adding our dev config.

Insert the following code at the end of the Dockerfile:

```Dockerfile
# DEV CONFIG
FROM prod as dev

EXPOSE 5000 3000

ENV NODE_ENV=development

RUN npm install -g nodemon

RUN npm install --only=dev

CMD [ "npm", "run", "dev" ]
```

Here we are simply reusing the prod config, overwriting some lines and adding extra config:

>Line 25 we reuse the current prod image and create a dev alias.
That is how our multistage is defined.

>Line 27 we expose the necessary ports for local development, allowing us to make requests to localhost:3000 and localhost:5000.

>Line 29 overwrites the NODE_ENV to use development environment.

>Line 31 we install nodemon globally, as we need it for local development.

>Line 33 installs all the dev dependencies if any.

>Line 35 we overwrite the run script.

<a name="dev-env-complete-dockerfile"></a>
At this point, the Dockerfile should look like the following:

```Dockerfile
# PROD CONFIG
FROM node as prod

WORKDIR /app

COPY package*.json ./

RUN npm install

WORKDIR /app/client

COPY ./client/package*.json ./

RUN npm install

WORKDIR /app

COPY . .

ENV NODE_ENV=production

CMD [ "npm", "start" ]

# DEV CONFIG
FROM prod as dev

EXPOSE 5000 3000

ENV NODE_ENV=development

RUN npm install -g nodemon

RUN npm install --only=dev

CMD [ "npm", "run", "dev" ]
```

<a name="dev-env-docker-compose"></a>
### Create a docker-compose file for dev environment

Now we need a docker-compose file to test our development environment, creating a simple mongoDB, network and volumes like we did for the prod environment, but now we simply specify the <strong>dev</strong> target.

Create a docker-compose.yml file in the root of the project with the following content:

```yml
version: "3.7"

services:

  node-react-starter-db:
    image: mongo
    restart: always
    container_name: node-react-starter-db
    ports:
      - 27017:27017 
    volumes:
      - node-react-starter-db:/data/db
    networks: 
      - node-react-starter-network

  node-react-starter-app:
    image: thisk8brd/node-react-starter-app:dev
    build: 
      context: .
      target: dev
    container_name: node-react-starter-app
    restart: always
    volumes: 
      - .:/app
    ports:
      - "5000:5000"
      - "3000:3000"
    depends_on:
      - node-react-starter-db
    environment:
      - MONGODB_URI=mongodb://node-react-starter-db/node-react-starter-db
    networks: 
      - node-react-starter-network

volumes: 
  node-react-starter-db:
    name: node-react-starter-db

networks: 
  node-react-starter-network:
    name: node-react-starter-network
```

<a name="dev-env-run"></a>
## Run development environment

![](https://media.giphy.com/media/fnmknmPn48ntVlqpv8/giphy-downsized-large.gif)

Now we can run the app with the following command in the root of your project:

```
docker-compose up --build
```

The first run will take a while because it will rebuild everything, adding the necessary changes.

For the next runs you can simply run without the --build tag and it will be way faster:

```
docker-compose up
```

Remember to always add the --build whenever you change between dev or prod test environments.


Now you can visit http://localhost:3000 and see the app running.

You can also make a POST request to http://localhost:5000/api/product with the following JSON content:

```json
{
  "name": "<product name>",
  "description": "<product description here>"
}
```

Now, you will be able to see a list of products rendered on the screen, like so:

![](https://thepracticaldev.s3.amazonaws.com/i/8n282icdca6e01esu7eb.png)

With this development environment, you are able to make any changes to the project and it will reflect in your app with a pretty nice hot reload.

<a name="deploy"></a>
# Heroku deployment

Now that we already have our dev and prod images, let's deploy this app to Heroku.

<a name="deploy-login"></a>
### First, let's login:

```
$ heroku container:login
```

<a name="deploy-create"></a>
### Now, we create an app

```
$ heroku create
```

After that, an app will be created and it will be available in your Heroku account.
You will also receive the name of the app created and its URL.

Visit your [heroku](https://dashboard.heroku.com/) account, enter the app you just created and click in configure add-ons.

In this page, search for mLab mongoDB and add it to your app.

<a name="deploy-image-tag"></a>
You can go back to the terminal and add a tag to the prod image to be able to deploy it to Heroku:

```
$ docker tag thisk8brd/node-react-starter-app:prod registry.heroku.com/<HEROKU-APP-NAME>/web
```


<a name="deploy-image-push"></a>
Push this image to Heroku registry:

```
$ docker push registry.heroku.com/<HEROKU-APP-NAME>/web
```

<a name="deploy-image-release"></a>
Now you can release the image with the following command:

```
$ heroku container:release web
```

This will start your app, it will be available in a minute and you will be able to open the app:

```
$ heroku open
```


###

###Yaaay! 

Your app was successfully deployed and it is up and running.


![](https://media.giphy.com/media/WUq1cg9K7uzHa/source.gif)


You can check my example live [here](https://hidden-springs-14394.herokuapp.com/).

And the source code is available [here](https://github.com/thisk8brd/node-react-starter/tree/docker).

I hope you can find this tutorial useful, and see you in the next one!

![](https://media.giphy.com/media/1lyivyo2xiwAKITwa9/source.gif)