Deploying a Vue.js Single Page App (including Router) with Docker

Creating single page apps has become a more frequently requested task of web developers (like me) and deployment in containers, across zones and under monitoring, seems like a natural step.

In this post we’re going to have a look at how to deploy a Vue.js SPA with docker.

Let’s start off by installing the vue-cli to create a boilerplate project. Make sure you have Docker installed as well.

My video course on Vue.js has just been released with PACKT:

Click here to check it out!

Installing the Vue CLI is just an npm install away:

npm install -g @vue/cli

Now we create a project with vue create projectName (I’ll choose vue-docker as my project name` and pick the Manually select features configuration option.

Next, we’ll arrow key down to Router, hit SPACE and ENTER. I chose to hit ENTER for the rest of the options as well.

After a quick installation, you should see output that looks a bit like this:

⚓  Running completion hooks...

🎉  Successfully created project vue-docker.
👉  Get started with the following commands:

 $ cd vue-docker
 $ npm run serve

Now we change to the directory that our app was set up at and verify it works with:

cd vue-docker
npm run serve

You should see something similar to the screenshot below in your browser at http://localhost:8080:

To create a docker container, we’ll need to create a Dockerfile that runs nginx, a high performance web server.


# Create the container from the alpine linux image
FROM alpine:3.7

# Add nginx and nodejs
RUN apk add --update nginx nodejs

# Create the directories we will need
RUN mkdir -p /tmp/nginx/vue-single-page-app
RUN mkdir -p /var/log/nginx
RUN mkdir -p /var/www/html

# Copy the respective nginx configuration files
COPY nginx_config/nginx.conf /etc/nginx/nginx.conf
COPY nginx_config/default.conf /etc/nginx/conf.d/default.conf

# Set the directory we want to run the next commands for
WORKDIR /tmp/nginx/vue-single-page-app
# Copy our source code into the container
COPY . .
# Install the dependencies, can be commented out if you're running the same node version
RUN npm install

# run webpack and the vue-loader
RUN npm run build

# copy the built app to our served directory
RUN cp -r dist/* /var/www/html

# make all files belong to the nginx user
RUN chown nginx:nginx /var/www/html

# start nginx and keep the process from backgrounding and the container from quitting
CMD ["nginx", "-g", "daemon off;"]

We also need to create the nginx config files that we’re referencing, nginx.conf and default.conf in the directory nginx_config:


user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/;

events {
    worker_connections  1024;

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    sendfile        off;

    keepalive_timeout  60;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;


server {
  location / {
      root /var/www/html;
      try_files $uri $uri/ /index.html;

The try_files line is significant, because if a file can not be found, we will serve the index.html file and let the Vue Router figure out which component to display. This is somewhat of a fallback to index.html.

Now let’s build our docker container and name it vue-docker-container:

docker build -t vue-docker-container .

and lastly we can run our container with:

docker run -p 8080:80 vue-docker-container

Now you should be able to see your app in your browser again, but this time it’s being run from with Docker and you are free to deploy your container where ever you please!

Modifying the Vue Router to use HTML5 history mode

In order to remove the # in the URL, we need to pass the mode option to the Vue router and set it to history like below:

import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import About from './views/About.vue'


export default new Router({
  mode: 'history',
  routes: [
      path: '/',
      name: 'home',
      component: Home
      path: '/about',
      name: 'about',
      component: About

Deploying to Docker without building “in container”

You don’t have to build your app inside the Dockerfile, you can also simply run npm run build and then copy your directory in with

COPY dist /var/www/html

in your Dockerfile. This also saves you the additional nodejs dependency, but that requires you to do one more step and if you have a team working on a project, they might use different versions to build which might add unwanted variations.

Thank you for reading! If you have any comments, additions or questions, please leave them in the form below! You can also tweet them at me

If you want to read more like this, follow me on feedly or other rss readers

3 thoughts on “Deploying a Vue.js Single Page App (including Router) with Docker”

  1. Hey dude – been looking around heavy for someone to go through the process of docker-izing a vue app. So glad I saw your face – I had come across something of yours in the past that helped a lot.

    Great writeup – just a question – how do you handle the separation between development and production in your setup?

    I’d like to have my apps be like how you have in your writeup – everything in docker. That way, once I’m done developing, I can just build in docker and everything’s all set. But I just want to see if you have any advice for keeping the hot-reload while developing, while still having the build option when you’re done developing and want to push live.

    Thank you.

    1. I’m four years late, I know, but to anybody reading this in 2022 or later, you have essentially two options.

      Your first option is to write two dockerfiles. Name one (the name is arbitrary, but this is typically a good pattern) and the other Then when you build, specify your build target when you run the build. e.g. docker build -t mytagname -f to build dev, and then docker build -t mytagname -f to build your prod image. You can also specify this in a docker-compose file like so:

      context: ./folder-name-here
      dockerfile: # you could also put here

      Your second option is to use multi-stage builds. ( They look like this:

      FROM node:14-alpine AS dev

      # Dockerfile dev things

      FROM alpine:3.7 AS prod

      # Dockerfile prod things
      (end Dockerfile)

      Then you can use it in docker-compose like this:

      context: ./folder-name-here
      target: dev # you could put “prod” here as well, but it’s the stage of your Dockerfile you want to target

      Personally, I prefer the first option so I did a bad job breaking down the second one, but yeah. Those are your options. Hopefully it’s enough for you to do some googling! Good luck!

Leave a Reply

Your email address will not be published. Required fields are marked *