In my last DevBlog, I explained the Node/Express/Docker API project I started. Today, I’m adding a Vue front-end client in to the mix. My current goal is to be able to develop the Vue.js client locally within a container and (if possible) get hot-reload working from within a container. Then another day soon, once my Vue app can do a few things, I’ll try to deploy it to production.
Creating the Vue ‘client’ project directory
I started off by using the Vue-CLI create command to build a new Vue project in the root of the node-docker repo called ‘client’.
Then, checking out what the Vue people suggest for running it in Docker, I found this handy little guide: Dockerize Vue.js App
Creating the Vue container’s Dockerfile
In the ‘Simple Example’ there, they demonstrate a Dockerfile that sets up a Vue container with the slimmed down Node Alpine image installing the http-server package to act as … well an http server, then copying in the Vue project package.json files, running npm install, then copying the Vue project into an ‘/app’ directory in the container, and finally building the Vue project. It then exposes port 8080 and runs the http-server.
Then, there’s a ‘Real-World Example’ which I think I can adapt to my needs here more adequately. I say that because it uses Nginx instead of that http-server. This example Dockerfile has two stages, a build stage to build the Vue project, then a production stage to copy the build into the container’s Nginx html hosting directory.
Sounds fair, that’s what this looks like:
That’s all well and good, but we already have an Nginx container in the node-docker project. Would it be redundant to have two Nginx containers, one for the front-end code and another for the back-end code? It feels like it. Instead, I’ve opted to modify the Nginx configuration we already have to serve both of them.
I found this really helpful article “Nginx Vue.js and Express with Docker” by Robert Zehnder. Granted, it’s a few years old, but that’s ok. It contains good examples of Nginx configuration files, one for dev and another for production environment.
Here, Mr. Zehnder uses the upstream directive to define the front-end and back-end containers. Here ‘dev-node’ and ‘dev-webpack’ are defined in his docker-compose, and so those terms are useful aliases for the container IP addresses which are then defined using upstream. Later in the file, the server directive’s location directives use the proxy_pass directive (yes, many directives involved, I know), to pass request traffic coming in to port 80 for the location’s URI over to the specified server.
So I kept the /api location that was already present in my nginx config, and simply added a root ‘/’ location directive that uses proxy_pass the same way.
Adding the Vue service to docker-compose files
After glancing around at a few different articles that describe various ways to do it, I’ve gleamed some combined inspiration to adding the Vue service to the docker-compose files in order to get a container built and spun up for the development environment.
The nginx service will use the configuration file as before, and have open port 80 and port 443 for http and https traffic respectively.
The new vue-app service will build from the Dockerfile in the ./client directory, with the ports option set to map 8080 on host and container. So the nginx container will get a ‘/’ (root) request on port 80, then proxy_pass it to the vue-app container on port 8080, and our vue-app container will map that port 8080 to it’s port 8080 inside…. at which point our running development server will pick it up and serve it.
So that means, instead of the ./client/Dockerfile doing this two part build process and hosting it’s own built vue app in it’s own nginx, in the dev environment it only needs to install the vue/cli, the package.json dependencies and copy in the project files…
We could still expose port 8080 and run the npm run serve command in the Dockerfile here. Instead, I’ve moved that logic to the docker-compose.dev.yml file. I’m hoping that makes this Dockerfile more reusable when it comes time to deploy to prod.
Here I bind mount a volume for the project files. This way changing source code in the ./client directory of our host machine will update those changes in the container. Then we set this odd-ball environment variable CHOKIDAR_USEPOLLING to true, based on the Chokidar package. This will help enable hot-reloading in the container the same way we’re used to developing with the local vue-cli/webpack dev server. Finally, we run the npm run serve command to start up the dev server within the container.
Spinning up the containers and testing
Running the docker-compose up command mentioned near the end of the README brings up the containers to test them out.
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
Once the containers spin up, the npm run serve command still needs time to build and start the dev server. However, less than a minute later, visiting http://localhost:8080 in your web browser should navigate to the vue-app frontend code…
Now making some change to your front-end code should show up in the web browser 10-30 seconds later…
For me on my minimal little development box (i5 4th gen, 16gb ram) the changes were hot-reloaded 23 seconds later…
So this isn’t exactly ideal because when developing with the normal local dev server that npm run serve creates, the changes are reflected on the browser’s page in less than 2 seconds.
I’m not sure if this is just because my dev PC is starved of resources or just because the vm that the container is running on is just slower …. or perhaps a little of both. Inspecting my task manager reveals a set of maxed out numbers..
So! I intend on testing this out while developing on my gaming computer that has twice the CPU power and RAM space. Perhaps buying some ram sticks and closing all other apps on my minimal dev box will help too.
In the meantime – it’s totally possible to run the npm run serve command as usual which will spin up the local dev server on the host machine from port 8081, available at localhost:8081, which will hot-reload almost instantly.
Thanks for reading, next up I’ll be getting the front-end vue app container making API calls to the express API container.
- Nginx directives
- Chokidar Polling