Contents
- Contents
- Intro
- Obtain
node_modulesDependencies - Start Development Scripts
- Optional Improvements
- 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:
$
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:
yarn install v1.22.5
[1/4] Resolving packages...
[2/4] Fetching packages...
...
You can notice node_modules being populated in your host:
$
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 theDockerfilewhich we will pull from Docker hub in order to run our container. You can check out other tags fornodeimages on: https://hub.docker.com/_/node/ -
--rmand-it:-
--rmtells docker to clean up the trailing container afteryarn installcompletes and the script succeeds. -
-itindicates interactive mode, which connectsstdin/stdoutof the container, redirecting input/output from/to your terminal shell.
-
-
-v,-wand-u:-
-v "$(pwd):/app": We're mounting the project's folder into/appinside the container. A common way to buildDockerfilefor anodeapp (for example for CI/CD or containerised deployments) is to useADDorCOPYdirective in yourDockerfile. 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 containerWORKDIRto/app, the same directory we mounted our code into, in order to make every command (e.g.yarn installoryarn start) wedocker runto 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-uset, we execute the command as the host's useruid, so we have full control over our host'snode_modules.
-
Start Development Scripts
Developments Server
To start development server and test your app, run:
$
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:

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 areactproject. Since the defaultreact-scripts startruns on port 3000, I want to map that port to my host's port, which could be arbitrary instead of3000(say for example you're running multiple front-end projects). This makes the development server accessible vialocalhost:3000. -
-e CHOKIDAR_USEPOLLING=true: With this option,nodefrom 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 insidereact-scripts. Take out this option if you don't want live polling for file changes. -
--env-file="$(pwd)/.env:reactas 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.envfile 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
...
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
or
...
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "jest --coverage",
If you would like to run test watch, simply use these configs:
$
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.
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:
#!/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:
$
chmod ug+x docker-yarn.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:
"devDependencies": {
"husky": "^5.1.3"
},
and say .husky/pre-push:
#!/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) 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.
$
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.
$
sudo ln -s /var/scripts/docker-yarn.sh /usr/local/bin/yarn
To confirm:
$
which yarn
outputs:
/usr/local/bin/yarn
and:
$
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:
$
yarn add date-fns
outputs
yarn add date-fns
yarn add v1.22.5
[1/4] Resolving packages...
[2/4] Fetching packages...
Double check:
$
ls -la node_modules | grep date-fns
outputs
drwxr-xr-x 209 user user 12288 Mar 9 22:02 date-fns
Next command:
$
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:
$
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:
$
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.
Top comments (0)