There’s been a frequent pattern that I’ve come across in maintaining local project dependencies across a team. Changing npm contexts can be taxing and when the complexity of maintaining node packages and configuration continues to get in the way of development workflow.
The amount of time it takes to install node modules adds up over time and can be reduced using docker caching. I came across an article that talks about the time saved using multi-stage docker builds and uses an external mounted volume for the node_modules
path.
Here is the v2 docker-compose.builder.yml file that will run the npm install and build routines:
version: '2'
services:
base:
image: node:11
volumes:
- .:/usr/src/service
- nodemodules:/usr/src/service/node_modules
working_dir: /usr/src/service
install:
extends:
service: base
command: npm i
build:
extends:
service: base
command: npm run build
volumes:
nodemodules:
external: true
To make it easier to run these tasks, here is the Makefile so we can run make install
:
install:
docker-compose -f docker-compose.builder.yml run --rm install
build:
docker-compose -f docker-compose.builder.yml run --rm build
dev:
docker-compose up
setup:
docker volume create nodemodules
You’ll notice the external volume, you’ll need to create that first by running make setup
before you can run make install
. This will map the local node_modules path to the container and with multi-step caching, will speed up subsequent installs.
Here’s the dockerfile I’m using for my VueJS application, this uses NGINX to serve the distributed files:
# build stage
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# production stage
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
The default.conf
file needed to be included with the NGINX configuration (be sure to change the server_name
to match your domain):
server {
listen 80 default_server;
listen [::]:80 default_server;
root /usr/share/nginx/html;
index index.html;
server_name you.server.com;
location / {
try_files $uri $uri/ @rewrites;
}
location @rewrites {
rewrite ^(.+)$ /index.html last;
}
location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
# Some basic cache-control for static files to be sent to the browser
expires max;
add_header Pragma public;
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
}
}
You can run the docker build with the dockerfile and default.conf in place which will allow you to run the container, here’s an example:
docker build -t todoubled/vuejs-client:latest .
docker run -it -p 8080:80 --rm --name dockerize-vuejs-app-1 todoubled/vuejs-client:latest
Lastly, here is the docker-compose v3 example for running the vueJS application in development:
version: '3'
services:
dev:
image: node:11
volumes:
- nodemodules:/usr/src/service/node_modules
- .:/usr/src/service
working_dir: /usr/src/service
command: npm run serve
ports:
- 8080:8080
volumes:
nodemodules:
external: true
To summarize, you can now run a few make commands to leverage the docker-compose file(s) and docker cache setup and in a more reliable, performant way.
make setup
: will create the nodemodules volumemake install
: will install the npm dependencies and cache them in the docker image for future checks.make dev
: will run the development environment task for localmake build
: will run the build and export files to the /dist folder
You now have a set of tools to help with local npm tasks and modules and a docker image for running the distributed source to check against for performance. My hope is that this will save a collective team time in the long run and provide a consistent environment to build on.