Dockerizing a Vue.js application


It is common these days to run front-end and back-end services inside Docker containers. The front-end service usually talks using a API with the back-end service.

In this post we will cover following:

  1. Setting up a Docker based development environment with hot reloading
  2. Building a production ready Docker image with API url passed using environment variable

Setting up a Docker based development environment with hot reloading

We will use Vue CLI 3 for creating a vue.js application.

You can install Vue CLI using the command mentioned below.

npm install -g @vue/cli

Create a new project

vue create myapp

Once the above command complete, change directory to myapp and run the npm run serve command. This will start the application on port 8080 – http://localhost:8080/

The port 8080 is usually used by Tomcat based Java applications so I like to configure a different port. To use a different port, you can either specify port using the command-line npm run serve -- --port 5000 or you can create a configuration file in your root project and specify port as shown below

module.exports = {
    devServer: {
      port: 5000
    }
  }

Now, application will be accessible at http://localhost:5000/.

Now, let’s add Dockerfile to the project root. This Dockerfile will be used for local development.

# base image
FROM node:10.15.0

# set working directory
RUN mkdir /usr/src/app
WORKDIR /usr/src/app

# add `/usr/src/app/node_modules/.bin` to $PATH
ENV PATH /usr/src/app/node_modules/.bin:$PATH

# install and cache app dependencies
COPY package.json /usr/src/app/package.json
RUN npm install
RUN npm install -g @vue/cli
# start app
CMD ["npm", "run", "serve"]

The above will create a Docker image with all the application dependencies. It does not house your application source code. That still lives on your machine.

The npm packages will be reused till you don’t modify package.json. Once you modify package.json then docker will invalidate all the layers starting from line 12. You only have to build new images when package.json file changes so that node modules are installed.

Next, we will build the Docker image. But before we do that let’s add node_modules directory in the .dockerignore file. This will speed up Docker build process as our local dependencies will not be sent to the Docker daemon.

To build the Docker image, please run the following command.

 docker build -t myapp .

Now, you can spin up the Docker container

docker run -it -v ${PWD}:/usr/src/app -v /usr/src/app/node_modules -p 5000:5000 myapp

The important point to note in the above command is the use of -v flag.

  1. The first -v flag mounts the source code in your current directory to the /usr/src/app directory inside the container.
  2. The second -v flag ensures that host node_modules does not override the node_modules of the container. To ensure that, we create a data volume for /usr/src/app/node_modules .If you will run the docker run command without the second -v flag then you will get error vue-cli-service: not found.

Now, open your browser and go to http://localhost:5000/. You should see your Vue application running. You can make a change in your editor and it will be instantly reflected in the browser. That is hot reloading in action.

Building a production ready Docker image with API url provided from outside

We will create a separate Dockerfile for the production. Let’s call it Dockerfile-prod

FROM node:10.15.0 as ui-builder
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 npm install
RUN npm install -g @vue/cli
COPY . /usr/src/app
RUN npm run build

FROM nginx
COPY  --from=ui-builder /usr/src/app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

The above makes use of the multi-stage build feature of Docker. The first half of the Dockerfile build the artifacts and second half use those artifacts and create a new image from them.

To build the production image, you will run the following command.

docker build -f Dockerfile-prod -t myapp-prod .

You will see that production image is much leaner compared to the development version. The production image is 1/10 of the production image.

docker images|grep myapp
myapp-prod                                                     latest                       7ba41397e863        4 minutes ago       110MB
myapp                                                          latest                       02d7437ead0e        24 minutes ago      1.12GB

You can run the container by executing the following command:

docker run -it -p 80:80 --rm myapp-prod

The application will now be accessible at http://localhost

Passing API URL environment variable

It is a common requirement for frontend applications to communicate to the backend via APIs. Vue.js supports environment variables that you can pass at the build time.

To pass environment variable to the Docker during the application build we will change the Dockerfile-prod to following.

FROM node:10.15.0 as ui-builder
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 npm install
RUN npm install -g @vue/cli
COPY . /usr/src/app
ARG VUE_APP_API_URL
ENV VUE_APP_API_URL $VUE_APP_API_URL
RUN npm run build

FROM nginx
COPY  --from=ui-builder /usr/src/app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

In the Dockerfile shown above we have added line number 9 and 10. When we will build the image we will have to pass VUE_APP_API_URL argument. This is then used by npm run build.

To build the image, we will run the following command.

docker build --build-arg VUE_APP_API_URL=http://api.example.com/api -f Dockerfile-prod -t myapp-prod .

Now you can access the VUE_APP_API_URL in your code using the process.env.VUE_APP_API_URL

To test this you can change App.vue as shown below.

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">

  </div>
</template>


import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'app',
  components: {
    HelloWorld
  },
  data(){
    return {
      apiUrl: process.env.VUE_APP_API_URL
    }
  }
}


<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

We exposed apiUrl at line number 16 through 19 and used it line number 4. Now in HelloWorld.vue, you can add a line below h1

<h2>API URL is {{ apiUrl }}</h2>

The application will render the apiUrl on the page.

Now rebuild the image and run the container.

The sourcecode for the example application is on Github vue-docker
That’s it for this post.

References

I referred to Michael Herman post on Dockerizing a React app – Link

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s