Search code examples
dockernginxdocker-composestatic-site

Dockerized Nginx: Can static assets be updated without stopping the container?


I have an Nginx container set up which serves assets for a static website. The idea is for the webserver to always stay up, and overwrite the assets whenever they are recompiled. Currently the docker setup looks like this:

docker-app

docker-compose.yml:

version: '3'
services:
  web:
    build: ./app
    volumes:
      - site-assets:/app/dist:ro
  nginx:
    build: ./nginx
    ports:
      - 80:80
      - 443:443
    volumes:
      - site-assets:/app:ro
      - https-certs:/etc/nginx/certs:ro
    depends_on:
      - web

volumes:
  site-assets:
  https-certs:

Web (asset-builder) Dockerfile:

FROM node:latest
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY ./ .
RUN npm run generate

Nginx Dockerfile:

FROM nginx:latest
RUN mkdir /app
COPY nginx.conf /etc/nginx/nginx.conf

The certbot container is managed separately and is not relevant to the problem I'm having, but the Nginx container does need to be able to mount the https-certs volume.

This setup seemed good, until I realized the site-assets volume would not be updated after first creation. The volume would need to be destroyed and re-created on each app deployment for this to work, requiring the Nginx container to be stopped to unmount the volume. So much for that approach.

Is there a way to manage application data in this setup without bringing the Nginx container down? Preferably, I would want to do this declaratively with a docker-compose file, avoid multiple application instances as this doesn't need to scale, and avoid using docker inspect to find the volume on the filesystem and modify it directly.

I hope there is a sane answer to this other than "It's a static site, why aren't you using Netlify or GitHub Pages?" :)


Solution

  • Here is an example that would move your npm run generate from image build time to container run time. It is a minimal example to illustrate how moving the process to the run time makes the volume available to both the running container at startup and future ones at run time.

    With the following docker-compose.yml:

    version: '3'
    services:
      web:
        image: ubuntu
        volumes:
          - site-assets:/app/dist
        command: bash -c "echo initial > /app/dist/file"
        restart: "no"
      nginx:
        image: ubuntu
        volumes:
          - site-assets:/app:ro
        command: bash -c "while true; do cat /app/file; sleep 5; done"
    volumes:
      site-assets:
    

    We can launch it with docker-compose up in a terminal. Our nginx server will initially miss the data but the initial web service will launch and generate our asset (with contents initial):

    ❯ docker-compose up  
    Creating network "multivol_default" with the default driver
    Creating volume "multivol_site-assets" with default driver
    Creating multivol_web_1   ... done
    Creating multivol_nginx_1 ... done
    Attaching to multivol_nginx_1, multivol_web_1
    nginx_1  | cat: /app/file: No such file or directory
    multivol_web_1 exited with code 0
    nginx_1  | initial
    nginx_1  | initial
    nginx_1  | initial
    nginx_1  | initial
    

    In another terminal we can update our asset (your npm run generate command):

    ❯ docker-compose run web bash -c "echo updated > /app/dist/file"
    

    And now we can see our nginx service serving the updated content:

    ❯ docker-compose up  
    Creating network "multivol_default" with the default driver
    Creating volume "multivol_site-assets" with default driver
    Creating multivol_web_1   ... done
    Creating multivol_nginx_1 ... done
    Attaching to multivol_nginx_1, multivol_web_1
    nginx_1  | cat: /app/file: No such file or directory
    multivol_web_1 exited with code 0
    nginx_1  | initial
    nginx_1  | initial
    nginx_1  | initial
    nginx_1  | initial
    nginx_1  | updated
    nginx_1  | updated
    nginx_1  | updated
    nginx_1  | updated
    ^CGracefully stopping... (press Ctrl+C again to force)
    Stopping multivol_nginx_1 ... done
    

    Hope this was helpful to illustrate a way to take advantage of volume mounting at container run time.