POSTS / Containerize Vue.js Application and Deploy on Server

Published: 2024-03-05

I’ve been struggling on deploying String10/ChinaOpenDataPortal-FrontEnd-Vue@9d43c36 for a whole day. Here’s some log of steps and issues.

Containerize Vue.js App

Build a docker image is easy. Following Vue CLI Guide is just everything you need.

I wrote a Dockerfile like this:

# build stage
FROM node:lts-alpine as build-stage
COPY ./ /app
WORKDIR /app
RUN npm install
RUN npm run build

# production stage
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /app
COPY docker/nginx.conf /etc/nginx/nginx.conf

with nginx.conf just as guide showing:

user  nginx;
worker_processes  1;
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;
events {
  worker_connections  1024;
}
http {
  include       /etc/nginx/mime.types;
  default_type  application/octet-stream;
  log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
  access_log  /var/log/nginx/access.log  main;
  sendfile        on;
  keepalive_timeout  65;
  server {
    listen       80;
    server_name  localhost;
    location / {
      root   /app;
      index  index.html;
      try_files $uri $uri/ /index.html;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
      root   /usr/share/nginx/html;
    }
  }
}

.dockerignore went like this:

**/node_modules
**/dist

Rewrite Environment Variables

I put a .env file for convenience, which causes a big problem. During build, environment variables will be injected into the application. The resulting static files just contain the content of my .env as hardcoded strings.

As StackOverflow user NehaM’s answer shows, we can put a dummy value as env-vars while build then replace it using a custom entrypoint. Here you can refer the entrypoint as a script executes as the docker container start running. With the help of entrypoint, we get a runtime configurable image without borthering from the origin .env file.

Here’s the script:

#!/bin/sh

ROOT_DIR=/app

# Replace env vars in files served by NGINX
for file in ${ROOT_DIR}/index.html ${ROOT_DIR}/assets/*.js;
do
  sed -i 's|VUE_APP_MY_VARIABLE_PLACEHOLDER|'${VUE_APP_MY_VARIABLE}'|g' $file
  # Your other variables here...
done

# Let container execution proceed
exec "$@"

Then update the Dockerfile:

# build stage
FROM node:lts-alpine as build-stage
COPY ./ /app
WORKDIR /app
RUN npm install
+ ARG VUE_APP_MY_VARIABLE=VUE_APP_MY_VARIABLE_PLACEHOLDER
RUN npm run build

# production stage
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /app
+ ARG POSTPROCESSING_SCRIPT=rewrite-env-vars.sh
+ COPY docker/${POSTPROCESSING_SCRIPT} /docker-entrypoint.d/${POSTPROCESSING_SCRIPT}
+ RUN chmod +x /docker-entrypoint.d/${POSTPROCESSING_SCRIPT}
COPY docker/nginx.conf /etc/nginx/nginx.conf

.dockerignore:

**/node_modules
**/dist
+ .env*

Push to Docker Hub

Run command:

docker build -f docker/Dockerfile -t USER_NAME/IMAGE_NAME .
docker push USER_NAME/IMAGE_NAME

Deploy on Server

Create docker-compose.yml:

version: '3.4'
services:
  frontend:
    image: 'USER_NAME/IMAGE_NAME'
    container_name: CONTAINER_NA<E
    restart: unless-stopped
    ports:
      - 'PORT:80'
    environment:
      - 'VUE_APP_MY_VARIABLE=XXXX'

Then run docker compose up -d to start a docker container.

Nginx Configuration

Refer to #34.