Create a real docker development enviroment for your NodeJS app
Tags: docker, tutorial, javascript, node
Published: 2022-09-25 18:28:53

This is my first post here. So I will try to explain how I started creating NodeJS applications on any machine with docker without installing NodeJS on the host first. Sounds good to you? Keep reading.

I am mainly a PHP full-stack developer, but I started working with NodeJS a while some time ago. I will not lie to you, docker has helped me a lot to keep my development environment more organized, but I needed more, I started thinking "why I must install NodeJs in my host first?" every tutorial I found for dockerizing a NodeJS application started by installing NodeJS in the host machine. I wanted to start programming on any machine with only docker, no installing additional programs/services at all. 

Maybe some things will be obvious for you, but I will try to make a tutorial like the one I would like to have found for saving me some time.

First we need to create a Dockerfile. A Dockerfile will allow us to create a new image, customized to our needs:
`Dockerfile`
```
###################
# Development
###################
1. FROM node:16.17.0-alpine as development
2. WORKDIR /app
3. USER node
4. EXPOSE 3000
5. ENV NODE_ENV=development
6. ENV NPM_CONFIG_PREFIX=/home/node/.npm-global
7. ENV PATH=$PATH:/home/node/.npm-global/bin
8. RUN npm install -g @nestjs/cli
9. ENTRYPOINT /bin/sh
```  
`I have a comment at first because in another post we will be creating the production image, a multistaging image.`
Let's explain what is all this:
1. Base image.
2. Setting out the working directory, we will see it later.
3. By using the least privileged user is safer, you can read more [here](https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#non-root-user) in a development environment should not be necessary, but it is a good practice.
4. It just saying we will be using a port for exposing a service, as much as I know this is kind of informative, it will not expose the port unless we say it explicitly when we run the container.
5. Environment variable, if we need to check it to know in our code in what environment are we running.
6. Ok in line 5 we change the user, we are not root anymore and we will need to install dependencies globally that is why we need to change the path for that global install. You can find out more [here](https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#global-npm-dependencies)
7. Ok you will need to make use of your global dependency by using the CLI, right? You do not want to write the full path, so you need to tell that tiny Linux where to find the commands you will be executing.
8. I will install nestjs globally, you can install anything you need.
9. How useful can be a container if we can not execute anything inside? Later on, we will launch the container and a prompt will be waiting for us, just wait a bit we will be there in a minute.

It is pretty safe to say we well need a database too, that is why we will packet our services in a `docker-compose.yml` file

`docker-compose.yml`
```
1. version: '3.9'
2. services:
3.   db:
4.     image: mysql:8.0.30
5.     container_name: mysql
6.     restart: unless-stopped
7.     ports:
8.       - 3306:3306
9.     env_file: .env
10.     environment:
11.       MYSQL_ROOT_PASSWORD: ${DATABASE_ROOT_PASSWORD}
12.       MYSQL_DATABASE: ${DATABASE_NAME}
13.       MYSQL_USER: ${DATABASE_USER}
14.       MYSQL_PASSWORD: ${DATABASE_PASSWORD}
15.     networks:
16.       - nestjs
17.     volumes:
18.       - ./volumes/mysql:/var/lib/mysql
19.       - ./mysql/init:/docker-entrypoint-initdb.d
20.   api:
21.     depends_on: db
22.     container_name: api
23.     restart: unless-stopped
24.     ports:
25.       - 3000:3000
26.     networks:
27.       - nestjs
28.     volumes:
29.       - ./app:/app
30.     build:
31.       context: .
32.       dockerfile: Dockerfile
33.       target: development
34.     env_file: .env
35.     environment:
36.       - DATABASE_URL=mysql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}
37.     depends_on:
38.       - db
39. networks:
40.   nestjs:
```
Lines 3 to 19 have nothing special. We are creating a MySQL image.

Line 29, do you remember line 2 from the dockerfile? Of course, you do! Now we are saying "ok docker, my project is on the current directory on a folder called `app` and we need the content of that folder on my container working directory". That is why we need a volume, we do not want to lose anything.

Line 30, we need to build our customized image. We will do it in a minute.

Line 36, I am using Prisma in my nestjs application so I will inject the connection string in the container. You can delete this line, it is just an example of what can you do.

Now we have two files: `Dockerfile` and `docker-compose.yml`, let's build the image by executing the command:
`COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f ./docker-compose.yml build` you will get something like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lkmr9vgrc9gb6dige83i.png)

Now, let's run the services:
`docker-compose run --rm --service-ports api`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4k6o22zxkmyfgkx7qc71.png)

There you are! You are inside the container and it is waiting for your orders, let's see what is in there:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vy956j9ds4l9i12ipd4v.png)

I have my source code, but you will have an empty directory waiting for you to create a wonderful application.

Now you can do what you usually did with your host. If you are using NestJS for example, your first step would be `nest new my-wonderful-app`

You can have one container for every node version you need, no more changing the nodejs version on your host computer.

Hope this is useful for someone.

Can you help us to do this better? Share with us in the comments.