skip to Main Content

My web-application consists of a vue frontend (purely client-side), a .NET backend and a postgres db. For hosting I’m using docker and docker-compose (my first time).

The setup consists of 4 containers.

  • postgres db
  • .net backend
  • vue frontend (not running, just the built files)
  • nginx instance

The nginx container serves as a reverse proxy for my backend and serves the static files for the frontend. I’m using only one container for both since I’m planning on hosting on a raspberry pi with limited resources and I also wanted to avoid coupling vue and nginx.

In order to achieve this, I’m mounting a named volume frontend-volume to read the frontend files from which previously is mounted to the static files built by the frontend image. I have copied (hopefully all) the relevant parts of the docker-compose file and the frontend dockerfile below. The full files are on GitHub:

Now my setup works fine initially but when I want to update some frontend-code, it just won’t apply these changes in the container since the volume that contains the frontend files already exists and contains data (my assumption). I’ve tried docker-compose up --build and docker-compose up --build --force-recreate. Building manually with docker-compose build --no-cache frontend and then docker-compose up --force-recreate doesn’t work either.
I had hoped these old files would just be overridden but apparently that’s not the case. The only way I found to get the frontend to update correctly is to delete the volumes with docker-compose down -v and then running the up command again. Since I also have a volume for my database, this isn’t a feasible solution unfortunately.

My goal was to have a setup that enables me to do a git pull on the raspi followed by a docker-compose up --build to update all the containers to the newest state while retaining the volumes containing the database-data. But that in itself might be wrong, I just want something comparable.

So my question: How can I create a file-only container for the frontend without having my files "frozen"?

Alternatively: what’s the correct way of doing this (is it just wrong on every level)?

Dockerfile:

FROM node:14 as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY ./ .

RUN npm run build

FROM alpine:latest as production-stage
COPY --from=build-stage /app/dist /app
VOLUME [ "/app" ]

docker-compose.yml:

version: '3'

services:
  nginx:
    container_name: nginx
    image: nginx:latest
    restart: always
    ports:
      - 5001:80
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - frontend-volume:/app:ro
  frontend:
    container_name: frontend
    build:
      context: ./frontend
      dockerfile: Dockerfile
    volumes:
      - frontend-volume:/app
volumes:
  frontend-volume:

I also tried this dockerfile:

FROM node:14 as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY ./ .

RUN npm run build

FROM alpine:latest as production-stage
VOLUME /app
# RUN rm -R /app/* uncommenting this doesn't work either, it fails with 'rm: can't remove '/app/*': No such file or directory'
COPY --from=build-stage /app/dist /app

2

Answers


  1. First and foremost you should know that containers should run a single master process, and if saving resources is on your mind, think about the fact that if you need to run two types of applications on the same container you’d have to create a special base image that would be hard to maintain feature and security wise, not to speak of using a more general container image that in the end might consume even more resources than two tailor made, small and concise images.

    Regards not being tied to nginx with your frontend, the buety of using container means you don’t have to install different pieces of software or versions of them directly on your machine and switching to node 16 from 14 for example is easy as changing your build stage base image, so I wouldn’t worry about it especially cause you have many guides if you want to switch back from nginx and find a production dockerfile in a pinch.

    My advice (cause I got a bit confused from your setup) is to build your frontend image with, first, your build stage as you’ve done and then in the ‘production stage’ copy the static files built in the ‘build stage’ to the appropriate nginx html folder (which is I think /usr/share/nginx/html ) copy the nginx.conf also to it’s location and specify in the nginx configuration file to proxy requests with /api to the backend url.

    On the other hand, if you currently want to debug fast with local mounted volumes, you could skip the ‘build stage’ and run the commands in it on your local machine then binding the created build files to nginx html folder (again /usr/share/nginx/html) as well as the nginx configuration file, both at run-time.

    Running like this enables you to debug fast without messing around with stages and configuration and when your finished, using the better option with the full pipeline that will "freeze" the files.

    Login or Signup to reply.
  2. A container, first and foremost, wraps a process; a "file-only container" doesn’t really make sense as a concept.

    Once you compile your Vue application, as far as the Nginx process is concerned, it’s just a bunch of files to be served. You can compile these into the Nginx image. A multi-stage build would be a very common approach to this. I wouldn’t really consider this "coupling" different parts of the application together; you have one step that uses one set of tools to build the application, and a second step that serves it as static files.

    # frontend/Dockerfile
    
    # First stage: build the Vue app.  (Probably exactly what you have now.)
    FROM node:14 as build-stage
    WORKDIR /app
    ...
    RUN npm run build
    
    # Final stage: build an image that can serve the application.
    # (Not just a bunch of files, an actual server.)
    FROM nginx
    COPY --from=build-stage /app/dist /usr/share/nginx/html
    # (The base image provides a correct CMD already)
    

    Then in your docker-compose.yml file, there isn’t a separate container for the built files; they are already included in the image.

    version: '3.8'
    services:
      nginx:
        build: ./frontend
        restart: always
        ports:
          - 5001:80
        volumes:
          - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
          - ./nginx/conf.d:/etc/nginx/conf.d:ro
          # no volumes: for the code; it's built into the image
      # no separate frontend container
    

    As a general rule, you shouldn’t put your code or other outputs from your build process in volumes. As you already note in the question, Docker will only copy content into a named volume the very first time a container runs, so using a volume here causes any updates to the application to be ignored (or to static files, or your node_modules directory, or …). This approach also doesn’t work in other container environments like Kubernetes, where getting a volume that can be shared between containers is actually a little tricky, and where the container system won’t automatically copy anything into a volume for you.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search