1-liner Docker commands for React project
Tags: docker, beginners, react
Published: 2021-03-10 09:53:34


---
title: 1-liner Docker commands for React project
published: true
description: Simple 1-liners to run React project inside a docker container for host without npm installed. No docker build required.
tags: #docker #beginner #react
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9i7b6r2yek09dsdkfkvk.png
---

---

# Contents

- [Contents](#contents)
- [Intro](#intro)
- [Obtain `node_modules` Dependencies](#obtain-raw-nodemodules-endraw-dependencies)
- [Start Development Scripts](#start-development-scripts)
    - [Developments Server](#developments-server)
    - [Running `jest` with `--watchAll`](#running-raw-jest-endraw-with-raw-watchall-endraw-)
- [Optional Improvements](#optional-improvements)
    - [Shell Script Wrapper](#shell-script-wrapper)
    - [Pre-commit Hooks with `husky`](#precommit-hooks-with-raw-husky-endraw-)
    - [Repo](#repo)
- [Final Thoughts](#final-thoughts)

---

# Intro

Let's say you're primarily a backend developer who works with `docker` frequently, and you don't want to have `nvm` and multiple `node` versions installed. You still want to checkout a frontend project, and maybe start a `node`/`react` frontend to see how your system connects, or maybe perform some static analysis on your IDE, these are some good one-liner `docker` commands to quickly get it up and running. No `node`/`npm` needed in your host, no custom `Dockerfile` or `docker build` process required.

---

# Obtain `node_modules` Dependencies

After pulling your project from source control, `cd /your/project/folder`, run the following `docker` command in a terminal:

```sh
$
docker run --rm -it -v "$(pwd):/app" -w "/app" -u $UID node:12-alpine yarn install
```
_**Note**: I'm using `yarn` here, if you're using `npm`, simply replace `yarn install` with `npm i` (shorthand) or `npm install`_

outputs:
```sh
yarn install v1.22.5
[1/4] Resolving packages...
[2/4] Fetching packages...
...
```

You can notice `node_modules` being populated in your host:
```sh
$
ls -la | grep node_modules
```

outputs

```
drwxr-xr-x 1050 user user  36864 Mar  9 20:27 node_modules
```

The idea is to run a container while volume-mounting our host folder to the container. As we obtain `node_modules` using `yarn install` from within the container, with volume mounting, we effectively persist it into our host workspace folder, which allows us to perform static analysis afterwards.

Let's break down this command so next time you can remember it instead of having to pull out your notes.

- `node:12-alpine`: Name of the `Dockerfile` which we will pull from Docker hub in order to run our container. You can check out other tags for `node` images on: https://hub.docker.com/_/node/
- `--rm` and `-it`:
  - `--rm` tells docker to clean up the trailing container after `yarn install` completes and the script succeeds.
  - `-it` indicates interactive mode, which connects `stdin/stdout` of the container, redirecting input/output from/to your terminal shell.
- `-v`, `-w` and `-u`:
  - `-v "$(pwd):/app"`: We're mounting the project's folder into `/app` inside the container. A common way to build `Dockerfile` for a `node` app (for example for CI/CD or containerised deployments) is to use `ADD` or `COPY` directive in your `Dockerfile`. However we're directly mounting the host files to avoid rebuilding the container at every change, for ease of development.
  - `-w "/app`": This parameter sets the container `WORKDIR` to `/app`, the same directory we mounted our code into, in order to make every command (e.g. `yarn install` or `yarn start`) we `docker run` to point at the folder.
  - `-u $UID`: With volume mounting, commands we execute inside the container's mounted volume will generate files that are then persisted back on the host's project folder. With `-u` set, we execute the command as the host's user `uid`, so we have full control over our host's `node_modules`.

---

# Start Development Scripts
### Developments Server
To start development server and test your app, run:

```sh
$
docker run --rm -it -v "$(pwd):/app" -w "/app" -u $UID -p 3000:3000 -e CHOKIDAR_USEPOLLING=true --env-file="$(pwd)/.env" node:12-alpine yarn start
```
outputs:
```
Compiled successfully!

You can now view react-docker in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://172.17.0.2:3000

Note that the development build is not optimized.
To create a production build, use yarn build.
```

And our app is up and running:
![App](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0mea5344lxxwtjt2zh3r.png)

Apart from the config parameters applied similarly to our `yarn install` script, we add a few more for development purposes.
- `-p 3000:3000`: I'm testing with a `react` project. Since the default `react-scripts start` runs on port 3000, I want to map that port to my host's port, which could be arbitrary instead of `3000` (say for example you're running multiple front-end projects). This makes the development server accessible via `localhost:3000`.
- `-e CHOKIDAR_USEPOLLING=true`: With this option, `node` from within the container will be able to watch any change in the project's mounted files, and reload the app accordingly with the configured webpack inside `react-scripts`. Take out this option if you don't want live polling for file changes.
- `--env-file="$(pwd)/.env`: `react` as well as many other front-end libraries want to make use of environment variables, for example for different build target, different feature flags, etc. This option will forward all the variables declared in your project's folder's `.env` file to the container environment variables, which can be convenient for testing. Take this option out if you don't use environment variables in your setup.

Notice how `react-scripts` is letting us know the development server is accessible via http://172.17.0.2:3000/. This is simply the container service provided ip address by docker default network. We don't have to concern ourselves with this because we'll never access it from within the container. And since the host port is mapped to the container's port, we can access it on our host computer browser via localhost:3000.

### Running `jest` with `--watchAll`
If you use `react-scripts` or `jest`, with the following configs in `package.json`
```json
...

  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
```
or
```json
...

  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "jest --coverage",
```
If you would like to run test watch, simply use these configs:
```sh
$
docker run --rm -it -v "$(pwd):/app" -w "/app" -u $UID -e CHOKIDAR_USEPOLLING=true --env-file="$(pwd)/.env" node:12-alpine yarn test --watchAll
```
outputs:
```
yarn run v1.22.5
$ react-scripts test --watchAll
```
```
 PASS  src/App.test.tsx
  ✓ renders learn react link (37 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.349 s
Ran all test suites.

Watch Usage
 › Press f to run only failed tests.
 › Press o to only run tests related to changed files.
 › Press q to quit watch mode.
 › Press p to filter by a filename regex pattern.
 › Press t to filter by a test name regex pattern.
 › Press Enter to trigger a test run.
```

_**Note**: `--watch`, while giving better performance isn't possible without `git` installed, which is the case for the pre-built node alpine image on dockerhub. For a more feature-rich containerised set-up, refer to this great article by Michael Herman: [Dockerizing a React App](#https://mherman.org/blog/dockerizing-a-react-app/)._

---

# Optional Improvements

### Shell Script Wrapper
You can notice how the parameters can be set similarly for each of our `yarn`/`npm` command. Thus, it would be reasonable to wrap them in a `bash` script, accept arbitrary arguments, so we can call the containerised `yarn` with any subcommand or parameters.

If you use the one-liner command, write this script, name it something like `docker-yarn.sh`:
```bash
#!/bin/bash

args="$@"
echo "yarn $args"
docker run --rm -i -v "$(pwd):/app" -w "/app" -u $UID -e CHOKIDAR_USEPOLLING=true --env-file="$(pwd)/.env" node:12-alpine yarn $args
```

_*Note*: Substitute `yarn` for `npm` if you use `npm`._

_*Note*: I'm using `-i` instead of `-it` for `husky`'s `git` hooks explained below._

`chmod` and run:
```sh
$
chmod ug+x docker-yarn.sh
```

```sh
$
./docker-yarn.sh install
```

### Git Hooks with `husky`
If you have a pre-commit hook like `husky` installed, this will be what your project likely has in `package.json`:
```json
  "devDependencies": {
    "husky": "^5.1.3"
  },
```
and say `.husky/pre-push`:
```sh
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

yarn run lint && yarn test --watchAll=false
```

Since you don't want to change your `devDependencies` (other team members might depend on it), and you want your `npm` to be dockerised, you can create a shell script similar to the above section ([Bash script wrapper](#bash-script-wrapper)) which wraps a dockerised `yarn`/`npm`, then alias/symlink it as `npm` for your host machine.

For demonstration, I'll put it inside `/var/scripts/docker-yarn.sh`. Then pick a directory that is included in your `PATH`.
```sh
$
echo $PATH
```
outputs
```
...:/usr/local/sbin:/usr/local/bin:...
```
One of mine includes `/usr/local/bin`, so I'll be symlink the `yarn` wrapper there.
```sh
$
sudo ln -s /var/scripts/docker-yarn.sh /usr/local/bin/yarn
```
To confirm:
```sh
$
which yarn
```
outputs:
```
/usr/local/bin/yarn
```
and:
```sh
$
cat $(which yarn)
```
outputs:
```
#!/bin/bash

args="$@"
echo "yarn $args"
docker run --rm -i -v "$(pwd):/app" -w "/app" -u $UID -e CHOKIDAR_USEPOLLING=true --env-file="$(pwd)/.env" node:12-alpine yarn $args
```
To make sure it is working, run a `yarn` command with arguments. Here I'm testing by installing a package, and running `yarn test --watch`

Installing package:
```sh
$
yarn add date-fns
```
outputs
```
yarn add date-fns
yarn add v1.22.5
[1/4] Resolving packages...
[2/4] Fetching packages...
```
Double check:
```sh
$
ls -la node_modules | grep date-fns
```
outputs
```
drwxr-xr-x  209 user user  12288 Mar  9 22:02 date-fns
```

Next command:
```sh
$
yarn test --watch
```
outputs
```
yarn test --watch
yarn run v1.22.5
$ react-scripts test --watch
```
```
PASS  src/App.test.tsx
  ✓ renders learn react link (52 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        3.283 s
Ran all test suites.

Watch Usage
 › Press f to run only failed tests.
 › Press o to only run tests related to changed files.
 › Press q to quit watch mode.
 › Press p to filter by a filename regex pattern.
 › Press t to filter by a test name regex pattern.
 › Press Enter to trigger a test run.
```
And finally our `git` hooks. Commit:
```sh
$
git commit -m "fix: wrapper script"
```
outputs
```
yarn run lint
yarn run v1.22.5
$ eslint --fix
Done in 0.41s.
[master f8e398c] fix: wrapper script
 Date: Wed Mar 10 20:37:36 2021 +1100
 1 file changed, 3 insertions(+), 4 deletions(-)
```
push:
```sh
$
git push origin master
```
outputs
```
yarn run lint
yarn run v1.22.5
$ eslint --fix
Done in 0.41s.
yarn test --watchAll=false
yarn run v1.22.5
$ react-scripts test --watchAll=false
PASS src/App.test.tsx
  ✓ renders learn react link (46 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.769 s
Ran all test suites.
Done in 4.06s.
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 413 bytes | 413.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:veevidify/react-docker.git
   ec7162b..f8e398c  master -> master
```

### Repo

Repository for reference: https://github.com/veevidify/react-docker

---

# Final Thoughts

If you work primarily with front-end projects like `react`, I would recommend using `nvm` instead of containerised, since any issue would be much easier to troubleshoot. However, this article aimed to show you how powerful `docker` can be, and with some basic understanding of image, container and volume, with a couple of clever configs, we can achieve almost any environment or setup.