Quick and dirty Docker development environment for Node.js apps
Tags: vscode, docker, node, programming
Published: 2021-04-19 07:59:44

[This article is also available on Medium.](https://danielgaldev.medium.com/quick-and-dirty-docker-development-environment-for-node-js-apps-70fa1a130714)

### Pre-requisites

- [Docker](https://docs.docker.com/get-docker/)
- [VSCode](https://code.visualstudio.com/download)
- [VSCode Remote - Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)

### Files

{% gist https://gist.github.com/danielgaldev/3d24d8bc449fc196bd0ac7105211dc24 file=.env %}

{% gist https://gist.github.com/danielgaldev/3d24d8bc449fc196bd0ac7105211dc24 file=docker-compose.yml %}

### Usage

Start the environment.

```bash
docker-compose up -d
```

Attach to the container in VSCode.

![0_naxjaKYP9HGtOdD7](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gm9rwb9y2s5jl79eecp0.png)
 
![0_9azzNKlDwo0c_weB](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ru00vshzy4kf16n6v7xm.png)
 
Enjoy! :)

When done, stop the environment.

```bash
docker-compose stop
```

### Why?

I wrote an article on the benefits of using a Dockerized development environment. Check it out here:

{% medium https://danielgaldev.medium.com/9-reasons-why-you-should-use-docker-as-a-development-environment-96e2ce154194 %}

### What is this good for?

A Node.js app can be:

- an Express backend (eg. a REST API)
- any JavaScript frontend — React, Angular, Vue
- a CLI app written in JavaScript
- any other type of JavaScript app

It is up to you what you make in this environment. I usually do React frontends.

### Tips

When you are attached to the container in VSCode, the IDE's terminal opens a terminal session inside the container. Here you can execute commands directly inside your container without the need to attach to it from another external terminal like PowerShell.

You can reduce the time spent starting and stopping the environment by shortening your Docker commands. If you have multiple concurrent projects, this will save you time and annoyance. I also wrote an article on how to do this, read it here:

{% medium https://danielgaldev.medium.com/speed-up-your-docker-workflow-with-bash-functions-45f522bd87a4 %}

After you opened the dev container once, you can return to it from the "Open recent..." (Ctrl+R) command of VSCode. You don't have to always do the "Attach to running container…" command.

You can extend this bare bones development environment with many different functionalities. I listed my suggestions here.

If you would like to install packages in the container, the best way is to create a Dockerfile, define the install commands inside and change the `image` property in the yml to `build` with the correct reference to the build context. Here is an example for ffmpeg and the Vercel CLI:

```docker
FROM node:14.5.0-alpine
RUN apk add ffmpeg
RUN npm i -g vercel
```

You could also install Git to get access to the version control tools inside VSCode while you are attached to the container. However, if you want to do version control efficiently inside the container, you will need to set up SSH and configure Git to be able to push your code. It is possible, but I am not a fan because it makes the setup more complex. I do version control on the host system in a separate VSCode window with the IDE's built-in Git tools and the Git Graph extension.

### Explained, line-by-line

```yaml
PROJECT_NAME=my-project
PORT=3000
```

Docker-compose can fetch the environment variables from a local `.env` file. The purpose of this is to avoid repetition in the `docker-compose.yml`.

```yaml
image: node:14.5.0-alpine
```

I use the Alpine version of the Node image simply because it is small (40 MB compared to the `:latest` 344 MB). I also use fixed versions to avoid the image upgrading itself and breaking something when I'm in a rush. This way I can upgrade the image when I have the time.

```yaml
working_dir: /usr/src/${PROJECT_NAME}
```

You can use any working directory you want inside the container, I just use `/usr/src` to avoid name collisions in the root of the filesystem. It also helps me differentiate the projects in VSCode's "Open recent..." menu — back when I used `/app` or `/code` everywhere, I didn't know which project's folder I'm about to open.

```yaml
env_file:
  - .env
```

The environment variables defined in `.env` will be available inside the container. This is where I put my config options, passwords and secret keys for the app.

```yaml
ports:
  - ${PORT}:${PORT}
```

There is a high chance I'm building a web app or a REST API, so I map a port of the container to the same port of the host system to be able to access my app or service in the browser.

```yaml
volumes:
  - /usr/src/${PROJECT_NAME}/node_modules
  - .:/usr/src/${PROJECT_NAME}
```

I am mapping the project directory to the container's working directory so that the edits I make in the container propagate to the host system where I do my version control. I don't want the content of `node_modules` leaking into my host system however, so I defined that folder as a volume on its own. Otherwise the npm installs would be noticeably slower because the files need to be copied from the container to the host.

```yaml
entrypoint: tail -F /dev/null
```

If I would start the container without this entrypoint, it would just exit and I would not be able to attach to it. This command makes the container run indefinitely.

For more advanced devcontainer options visit VSCode's [Developing inside a Container](https://code.visualstudio.com/docs/remote/containers) article.

Thank you for reading to the end and have a wonderful day :)

_Cover photo by [Jefferson Santos](https://unsplash.com/@jefflssantos?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com/?utm_source=medium&utm_medium=referral)_