Enabling Hot-Reloading with vuejs and vue-cli in Docker

This is a quick post on how to get hot module reloading working in vuejs/vue-cli in a local dockerized development setup based on a virtual machine in Virtualbox. The very short answer is to pass the environment variable CHOKIDAR_USEPOLLING=true to the container.

Background

When using a so-called bind mount in Docker "a file or directory on the host machine is mounted into a container" (see here for differences between bind mounts and named volumes)

For dockerized software development with e.g. a MacBook or a Laptop and a VM in Virtualbox (for the Docker Host) you usually pass through a directory from your MacBook to the Docker Host in the virtual machine via Shared Folders and then this (or some directory beneath it) further into the container at runtime via a so-called bind mount.

In contrast to a named volumes you can write code on your MacBook and the changes are immediately available in the container. However, detecting file system changes in such a construct (Mac OS => Docker Host VM => Docker Container) sometimes does not work as if everything was on the same (physical) system with same OS and file system.

App Setup

This post assumes that you use the new vue-cli ... however the basic principles just outlined above should be true also for other setups (indeed it is also true for a react app, see here and here):

npm install -g @vue/cli
vue create my_app
# stick to the default here or use a custom setup

With this in place you can now configure the docker development settings.

Docker Setup

A basic Dockerfile for local development could look like that:

FROM node:carbon-slim
# vue-cli reqires 8.10.0+ 

RUN apt-get -y update \
  && apt-get install -y git

RUN npm install -g @vue/cli

WORKDIR /target/in/container

EXPOSE 8080

USER node

CMD ["yarn", "serve"]

The important bits in docker-compose:

version: "3.3"

services: 
  my_app:
    <...>
    ports:
      - "8080:8080"
    volumes:
      - /path/on/laptop:/target/in/container
    stdin_open: true
    tty: true
    environment:
      - <...>
      - CHOKIDAR_USEPOLLING=true
    <...>

The most important bit is CHOKIDAR_USEPOLLING=true. Details on what it is and what it does can be found here.

You could of course pass the bind mount and the environment variable just as well on the docker cli during docker run <...> or even set the environment variable CHOKIDAR_USEPOLLING=true with ENVin your Dockerfile.

Summary

With this setup you now have (with only one additional environment variable) all you need to write code on your laptop which gets detected immediately in the container and leads to a restart of the webpack-dev-server.

Add-On

If you get sockjs-node errors in your browser console you can add the following to your vue.config.js in the root directory of your app:

module.exports = {
  configureWebpack: {
    <...>
  },
  devServer: {
    public: '<docker_host_external_ip>:8080',
  },
};

As an alternative to CHOKIDAR_USEPOLLING you can also try the watchOptions approach:

devServer: {
  watchOptions: {
    ignored: /node_modules/,
    aggregateTimeout: 300,
    poll: 1000,
  },
}

However, as the docs note:

If watching does not work for you, try out this option. Watching does not work with NFS and machines in VirtualBox.

Further Information:

This post was inspired by:
http://mherman.org/blog/2017/12/07/dockerizing-a-react-app/

Chokidar:
https://github.com/paulmillr/chokidar

Vue.js and vue-cli:
https://vuejs.org
https://github.com/vuejs/vue-cli
https://cli.vuejs.org
https://github.com/webpack/webpack-dev-server
https://webpack.js.org/configuration/watch/#watchoptions

Docker Bind Mounts and Volumes:
https://docs.docker.com/storage/bind-mounts/
https://docs.docker.com/storage/volumes/

React App Similarities:
https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#troubleshooting