Contents
- Contents
- Intro
- Obtain
node_modules
Dependencies - 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 theDockerfile
which we will pull from Docker hub in order to run our container. You can check out other tags fornode
images on: https://hub.docker.com/_/node/ -
--rm
and-it
:-
--rm
tells docker to clean up the trailing container afteryarn install
completes and the script succeeds. -
-it
indicates interactive mode, which connectsstdin/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 buildDockerfile
for anode
app (for example for CI/CD or containerised deployments) is to useADD
orCOPY
directive 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 containerWORKDIR
to/app
, the same directory we mounted our code into, in order to make every command (e.g.yarn install
oryarn start
) wedocker 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 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 areact
project. Since the defaultreact-scripts start
runs 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,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 insidereact-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
...
"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)