How to use docker for development
Tags: docker, vscode, tutorial
Published: 2022-06-02 17:47:40

Docker for development environment

{% embed https://www.youtube.com/embed/NqoYKwPQl6Ug %}
_See this and many other articles at [lucaspaganini.com](lucaspaganini.com)_

## Short Version

To fix environment compatibility issues, you could set up a docker environment for development. To do that, create a dockerfile and put all of your project environment dependencies there, things like NodeJS, Git, Python, so on… You can also use docker-compose if you depend on other containers.

Now create a docker volume mapping to your source code and use your development container to run all your commands.

If you're using VSCode, there's an extension that makes all that integrate much better, I'll leave a link for it in the references.

If that short version was enough for you, just leave a like and have a great day! Stick around if you want a deep dive.

## Tech Introduction

We love Docker, but apparently, we hate our coworkers. Think about it. It's frustratingly common to join a project and go through a full day of onboarding.

But hey, we're getting paid, right? Unless it's Open Source, in that case we just give up trying to contribute.

Sure, it's good to have a guide to the codebase, but it shouldn't take that long. It's just an introduction, after all, you'll only really get familiar with the codebase when you start working on it.

And that's the frustrating part, "working on it". It takes too long to set up the development environment.

You need that specific Node version, and that specific Python version, and an NGINX server running and a Mongo DB populated, and so on… Switching machines becomes infeasible.

Now, imagine joining a project and the only thing you need to have installed is Docker. Then you run a single command, and it's all ready.

That's what we're going to do today.

I'll show you how to set up the development environment for a project that uses Node, Heroku, and MongoDB. When it's done, we'll be running `npm install`, `npm run build`, and `npm start` from inside a container.

## Setup for a Node project

So, our project requires Node. But it's not any Node, it's Node version 14.15.4. And NPM 7.5.4 and the latest Heroku CLI available.

Ok, let's write our dockerfile.

```dockerfile
FROM node:14.15.4
RUN npm install -g npm@7.5.4
RUN curl https://cli-assets.heroku.com/install.sh | sh
WORKDIR /var/www
```

Ok, done.

NOTE: That would take at least 15 minutes for any new developers to join your team. And they would constantly run your project with the wrong Node version. Don't thank me later, thank me now.

## Connecting to the container

Now let's build that container and run it interactively and with TTY.

```bash
docker build --tag dev-container .
docker run --interactive --tty dev-container
# ↑ Same as docker run -it dev-container
```

NOTE: By the way, I don't know what TTY stands for, I'm sure someone will [send a tweet](https://twitter.com/LucasPaganini) enlightening us. But if you don't use TTY, your container will run, and die... instantly…
Unless the command from the image that you're running keeps it alive. But images like Ubuntu don't stay alive by default, so you need TTY for those cases.

Now we're inside the container's shell, and we can verify that Node, NPM, and the Heroku CLI are correctly installed.

```bash
root@90967edcf8f1:/var/www node -v
v14.15.4

root@90967edcf8f1:/var/www npm -v
7.5.4

root@90967edcf8f1:/var/www heroku -v
heroku/7.47.12 linux-x64 node-v12.16.2
```

If you kill your process, the container will die too. If that's not what you want, you can run the container detached and use docker exec to connect to it. That way, it won't die when you kill your process.

```bash
docker run --detach --tty dev-container
# Use `docker ps` to grab the container ID
docker exec --interactive --tty { container_id } bash
```

## Sharing files with volumes

Our container has the tools that we need, but it doesn't have access to our source code yet.

To fix this, we'll create a docker volume when we run it.

```bash
# Use `docker ps` to grab the container ID
docker kill { container_id }
docker run --detach --tty --volume $(pwd):/var/www dev-container
# Use `docker ps` to grab the new container ID
docker exec --interactive --tty { new_container_id } bash
```

We can now access our project files inside the container in `/var/www`.

```bash
root@90967edcf8f1:~ cd /var/www
root@90967edcf8f1:/var/www ls
dist  node_modules  package-lock.json  package.json  src  tsconfig.json
```

Let's run `npm clean-install` and then `npm run build`, just for the fun of seeing it work.

## VSCode integration

Nice, it works!

But running the container and attaching it to it is a pain in the ass.

Also, if you try to use Git inside the container, you'll notice that you're not authenticated to your remote repository. That's another huge turn-off.

The good news is that if you're using VSCode, as all the other cool kids are, you can avoid those issues and make the whole process almost seamless.

Install the `Remote - Containers` extension. That VSCode extension will automatically build, run and attach the VSCode terminal to your development container shell. It will also handle your git credentials, kill the container when you close VSCode and do some other nice things to make your life easier.

To configure that extension, we need to create a `devcontainer.json` file inside the `.devcontainer` folder and also move our dockerfile to that folder. The properties are very clear and the complex ones are better explained in their documentation, so I won't attempt to explain them.

```json
// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.134.0/containers/javascript-node
{
  "name": "lucas-paganini-api",

  "build": {
    "dockerfile": "./Dockerfile",
    "context": ".."
  },
  "forwardPorts": [3000],

  "workspaceMount": "source=${localWorkspaceFolder},target=/var/www,type=bind",
  "workspaceFolder": "/var/www",

  "postStartCommand": "npm clean-install",
  "shutdownAction": "stopContainer",

  "settings": {
    "terminal.integrated.shell.linux": "/bin/bash"
  },

  // Add the IDs of extensions you want to be installed when the container is created.
  "extensions": ["ms-vscode-remote.remote-containers", "esbenp.prettier-vscode"]
}
```

NOTE: You can also declare an array of VSCode extensions that you want to have installed in the development environment. And you can also declare a command to run when the container is ready! Honestly, isn't that awesome?!

When you open the project, it will show a popup message asking if you want to open it inside the dev container. If that doesn't happen, open the VSCode command palette and search for "Remote-Containers: Open Folder in Container", then select the project root folder.

## Adding MongoDB with Docker Compose

But what if you depend on other containers? For example, a mongo DB. In that case, you'd need a docker-compose file and a few changes to the devcontainer.json.

```yml
# Docker compose for development

version: '3.8'

services:
  mongo-db:
    image: mongo:4.4.3
    networks:
      - main-net

  main:
    build:
      dockerfile: ./.devcontainer/Dockerfile
      context: ..
    image: lucas-paganini-api/main
    ports:
      - '3000:3000'
    networks:
      - main-net
    environment:
      DB_HOST: mongo-db:27017
      PORT: 3000
    depends_on:
      - mongo-db
    tty: true
    volumes:
      - ..:/var/www
    command: bash

networks:
  main-net:
    driver: bridge
```

```json
// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.134.0/containers/javascript-node
{
  "name": "lucas-paganini-api",

  "dockerComposeFile": "./docker-compose.yml",
  "forwardPorts": [3000],
  "service": "main",

  "workspaceFolder": "/var/www",
  "postStartCommand": "npm clean-install",
  "shutdownAction": "stopCompose",

  "settings": {
    "terminal.integrated.shell.linux": "/bin/bash"
  },

  // Add the IDs of extensions you want to be installed when the container is created.
  "extensions": ["ms-vscode-remote.remote-containers", "esbenp.prettier-vscode"]
}
```

## Conclusion

I'm sure your projects will benefit from having a normalized development experience, and now you have no excuse to not do it.

I'll leave a link to the repository in the references below.

As always, have a great day and see you soon!

## References

1. [Repository](https://github.com/LucasPaganini/published-content/tree/master/docker-dev-env)
2. [How to Setup Your Local Node.js Development Environment Using Docker](https://www.docker.com/blog/how-to-setup-your-local-node-js-development-environment-using-docker/)
3. [How To Setup Your Local Node.js Development Environment Using Docker – Part 2](https://www.docker.com/blog/how-to-setup-your-local-node-js-development-environment-using-docker-part-2/)
4. [Why and How To Use Docker for Development](https://betterprogramming.pub/why-and-how-to-use-docker-for-development-a156c1de3b24)
5. [Using Containers for Development](https://docs.docker.com/language/nodejs/develop/)
6. [VSCode Extension for Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
7. [devcontainer.json Reference](https://code.visualstudio.com/docs/remote/devcontainerjson-reference)