Mounting Docker Volume On Nuxt Generate Directory
February 11th 2020
If you followed along with the tutorial from last week on Mutli Stage Docker Builds for Nuxt Generate we walked through installing a basic Nuxt.js project, and setting up a Dockerfile
which could be used to build an image that would run the nuxt generate
command for our project, copy the /dist
files over to the root directory of our nginx web server and start our nginx server to serve the static content generated by our nuxt generate command. We where then able to build the image and run the docker images (as containers) which executed our run time command to start nginx and serve our content. Following along with the tutorial the command to run the image (aka create a container) was:
docker run --name basic_nuxt --rm -d -p 3333:80 nuxt:nginx
This starts a container named basic_nuxt
and binds the host port 3333 to the exposed container port of 80 (which is the default port that nginx listen’s on). This works great, but we run into an issue when we make a change in our project and re-run the nuxt generate
command, since our files are only copied over once at build time, the new files are not mounted on our nginx root directory and will not show the changes. In order to view the changes we would have to stop our container and restart the container to view the changes. This can get annoying if we are making changes, so the logical step would be to mount a volume to the nginx root directory. What’s a volume you might be wondering? According to the Docker Docs:
Volumes are the preferred mechanism for persisting data generated by and used by Docker containers.
docs.docker.com
This sounds like what we need as our container would persist the data that is generated to our /dist
folder and serve the changes in our mounted directory, and in doing so we won’t need to restart our container!
At first glance I thought this would be easy. Since our nginx root directory in our container was located at /usr/share/nginx/html
I thought we could just bind our volume on to this directory such as:
docker run --name basic_nuxt --rm -d -p 3333:80 -v ${pwd}/dist:/usr/share/nginx/html:ro nuxt:nginx
This works fine the first time, until we run the nuxt generate
command again and if you reload the browser at http://localhost:3333 you would expect to see the changes that were generated in our /dist
folder of our project but instead you see a big, blank 404 page 🤔
The reason this is happening is because the nuxt generate
command removes the /dist
folder and rebuilds it, and since it’s removed the reference to the docker volume is lost and nginx no longer recognizes the path to the directory so it serves a 404 error. What to do, what to do?
The solution as presented in this github issue would be to set the configuration for the /dist
folder in nuxt generate
to be a subdirectory of a parent docker
folder (ie docker/dist
) and then bind the volume to the ${pwd}/docker
folder instead of the dist
folder the reference to the volume wouldn’t be lost and nginx would be able to serve the updated contents of dist
. In order to do that we would need to change the server root directory in our nginx.conf file to point to /usr/share/nginx/html/dist
instead of /usr/share/nginx/html/
and we would also need to change our Dockerfile to copy files from our Stage 1 build at /usr/src/app/docker
to /usr/share/nginx/html/
(which copied the docker folder and all files/subdirectories into /usr/share/nginx/html
). Let’s start with making edits to our nuxt.config file and then we’ll jump over to edit our Dockerfile and finally show you how to configure nginx.conf. In our nuxt.config.js
file we need to add a generate property like:
export default {
generate: {
dir: 'docker/dist'
},
}
We can confirm this is working by running yarn generate
and you should see a docker/dist
folder is generated in our project. Now onto our Dockerfile. To refresh your memory here is the old Dockerfile:
### STAGE 1: Build ###
FROM node:latest as build
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
ENV PATH /usr/src/app/node_modules/.bin:$PATH
COPY package.json /usr/src/app/package.json
RUN yarn install --silent
COPY . /usr/src/app
RUN yarn run generate
### STAGE 2: NGINX ###
FROM nginx:stable-alpine
COPY --from=build /usr/src/app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
We need to make a subtle change in our Dockerfile to make this work:
### STAGE 1: Build ###
FROM node:latest as build
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
ENV PATH /usr/src/app/node_modules/.bin:$PATH
COPY package.json /usr/src/app/package.json
RUN yarn install --silent
COPY . /usr/src/app
RUN yarn run generate
### STAGE 2: NGINX ###
FROM nginx:stable-alpine
COPY --from=build /usr/src/app/docker /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Note the COPY
command in STAGE 2 changed. This will copy everything under our ./docker
folder (including the dist
folder) into /usr/share/nginx/html/
. Perfect, we’re almost there. Now we just need to edit our nginx.conf file. First, where is our nginx file and how do we edit it? Well at run time our STAGE 2 will download the nginx image from Docker Hub which includes the nginx.conf file at /etc/nginx/nginx.conf
so we need to create a file in our local (host) directory and either copy that over at build time or we can mount it as a volume during run time (when a container is started). Since we’re going to mount a volume at runtime (for our ${pwd}/docker
folder we can mount the volume for the nginx.conf file as well at run time. Either way works but in this tutorial we’re choosing the later. Regardless, we need to create an nginx.conf file in our host directory. I would recommend downloading the nginx.conf file from the nginx repo on github: https://github.com/nginx/nginx/blob/master/conf/nginx.conf
Now that we have that we need to make an edit in our nginx.conf. Open that up in your editor of choice and scroll down to the server
block. Here we’ll edit the location /
block such that it will be:
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root /usr/share/nginx/html/dist;
index index.html index.htm;
}
...
}
Note the line: root /usr/share/nginx/html/dist;
which tells nginx that our root directory is now the dist
folder on not the html
folder. Perfect with that out of the way all we need to do is rebuild our image and then change the docker run
command to mount our volumes. You can either remove the nuxt:nginx image that we created and regenerate it or you can create a new image name. I’m going to remove and regenerate:
docker rmi nuxt:nginx && docker image prune
Then regenerate it. To do so make sure your in the project directory with the Dockerfile in it:
docker build -t nuxt:nginx .
Now that our image is built we are ready to start our container. If you remember the previous run command was:
docker run --name basic_nuxt --rm -d -p 3333:80 nuxt:nginx
Let’s tweak that to mount our volume and nginx.conf file:
docker run --name basic_nuxt \
--rm -d -p 3333:80 \
-v $(pwd)/docker:/usr/share/nginx/html/:ro \
-v $(pwd)/nginx.conf:/etc/nginx/nginx.conf \
nuxt:nginx
I broke this into multiple lines for readability. The changes that we’re looking at are -v $(pwd)/docker:/usr/share/nginx/html/:ro
and -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf
which are just mounting our ./docker
directory to the nginx root directory and mounting the local nginx.conf
to the containers nginx.conf
file. We can navigate to http://localhost:3333/ and you will see our basic nuxt project. Now make some changes in pages/index.vue
and save and run yarn generate
then navigate back over to http://localhost:3333/ and you will see the changes persisted in our container! Hot dang, that’s cool! There you have it. Until next time, stay curious, stay creative!