Search code examples
angulardockernginxtraefik

How to serve static files to Dockerized Angular apps on the same domain using Nginx and Traefik?


What I’d like to do is have 2 Angular apps on the same domain, but serve different files for different paths. For example, browsing to “app1/” will show different content than that on the “app1/test” path. Note: these are default Angular apps, they’re not doing anything special. The only thing I’ve changed the landing page to say “app1” or “app2” to verify that paths hit the correct container.

Additionally, I want anything on the app1/test path to resolve to the same host. So paths like “app1/test” and “app1/test/page1” will lead to the same container. I can get to app1/ with no issues, but I can’t seem to figure out how to route to app1/test correctly. I can browse to it, but it serves the content of app1/ instead of what it’s supposed to. I’ve verified that all of the files I’m creating are inside the container as well, they’re just not being accessed.

Finally, the URL information needs to be in a state that allows nginx to route to the correct container and also allow the angular apps to handle its own virtual routing, without these two interfering with one another.

Here are my config files:

docker-compose.yml


services:

  traefik:
    image: "traefik:v2.4"
    container_name: "traefik"
    command:
      - "--log.level=DEBUG"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
      - "8080:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
  app1:
    build:
      context: ./app1
    ports:
      - "8081:80"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app1.entrypoints=web"
      - "traefik.http.routers.app1.rule=Host(`localhost`)"
      - "traefik.http.routers.app1.middlewares=app1-stripprefix"
      - "traefik.http.routers.app1.middlewares=app1-autodetect"
      - "traefik.http.middlewares.app1-stripprefix.stripprefix.prefixes=/"
      - "traefik.http.middlewares.app1-autodetect.contenttype.autodetect=false"
      - "traefik.port=80"

  app2:
    build:
      context: ./app2
    ports:
      - "8082:80"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app2.entrypoints=web"
      - "traefik.http.routers.app2.rule=Host(`localhost`) && PathPrefix(`/test{regex:$$|/.*}`)"
      - "traefik.http.routers.app2.middlewares=app2-stripprefix"
      - "traefik.http.routers.app2.middlewares=app2-autodetect"
      - "traefik.http.middlewares.app2-stripprefix.stripprefix.prefixes=/test"
      - "traefik.http.middlewares.app2-autodetect.contenttype.autodetect=false"
      - "traefik.port=80"

Dockerfile for both Angular apps


# set working directory
WORKDIR /app

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

# install and cache app dependencies
COPY package.json /app/package.json
RUN npm install
RUN npm install -g @angular/[email protected]

# add app
COPY . /app

# generate build
RUN ng build --output-path=dist

# base image
FROM nginx:1.16.0-alpine

# copy artifact build from the 'build environment'
COPY --from=build /app/dist /usr/share/nginx/html
COPY ./nginx/nginx.conf /etc/nginx/conf.d/default.conf

# expose port 80
EXPOSE 80

# run nginx
CMD ["nginx", "-g", "daemon off;"]

nginx.conf for app1

include /etc/nginx/mime.types;
server {
    listen       80;
    server_name  localhost;
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

}

nginx.conf for app2

include mime.types;
include /etc/nginx/mime.types;
server {
    listen       80;
    server_name  localhost;
    location /test {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

Solution

  • I solved my problem. There were a few edits I made along the road, but the final key to the puzzle was a "try_files" line in my nginx.conf files. Read about it here: https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/

    Here are the updated files:

    docker-compose.yml

    
    services:
    
      traefik:
        image: "traefik:v2.4"
        container_name: "traefik"
        command:
          - "--log.level=DEBUG"
          - "--api.insecure=true"
          - "--providers.docker=true"
          - "--providers.docker.exposedbydefault=false"
          - "--entrypoints.web.address=:80"
        ports:
          - "80:80"
          - "8080:8080"
        volumes:
          - "/var/run/docker.sock:/var/run/docker.sock:ro"
      app1:
        build:
          context: ./app1
        ports:
          - "8081:80"
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.app1.entrypoints=web"
          - "traefik.http.routers.app1.rule=Host(`localhost`)"
          - "traefik.http.routers.app1.middlewares=app1-stripprefix"
          - "traefik.http.routers.app1.middlewares=app1-autodetect"
          - "traefik.http.middlewares.app1-stripprefix.stripprefix.prefixes=/"
          - "traefik.http.middlewares.app1-autodetect.contenttype.autodetect=false"
          - "traefik.port=80"
    
      app2:
        build:
          context: ./app2
        ports:
          - "8082:80"
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.app2.entrypoints=web"
          - "traefik.http.routers.app2.rule=PathPrefix(`/test{regex:$$|/.*}`)"
          - "traefik.http.routers.app2.middlewares=app2-stripprefix"
          - "traefik.http.routers.app2.middlewares=app2-autodetect"
          - "traefik.http.middlewares.app2-stripprefix.stripprefix.prefixes=/test"
          - "traefik.http.middlewares.app2-autodetect.contenttype.autodetect=false"
          - "traefik.port=80"
    

    Dockerfile (for app1)

    FROM node:14.15.4 as build
    
    WORKDIR /app
    
    ENV PATH /app/node_modules/.bin:$PATH
    
    COPY package.json /app/package.json
    RUN npm install
    RUN npm install -g @angular/[email protected]
    
    COPY . /app
    
    RUN ng build --output-path=dist
    
    FROM nginx:1.16.0-alpine
    
    COPY --from=build /app/dist /usr/share/nginx/html
    COPY ./nginx/nginx.conf /etc/nginx/conf.d/default.conf
    
    EXPOSE 80
    
    CMD ["nginx", "-g", "daemon off;"]
    

    Dockerfile for app2

    FROM node:14.15.4 as build
    
    WORKDIR /app
    
    ENV PATH /app/node_modules/.bin:$PATH
    
    COPY package.json /app/package.json
    RUN npm install
    RUN npm install -g @angular/[email protected]
    
    COPY . /app
    
    RUN npm install
    RUN npm install --save-dev @angular-devkit/build-angular
    RUN ng build --base-href /test --deploy-url /test/ --output-path=dist
    
    FROM nginx:1.16.0-alpine
    
    COPY --from=build /app/dist /usr/share/nginx/html/test
    COPY ./nginx/nginx.conf /etc/nginx/conf.d/default.conf
    
    EXPOSE 80
    
    CMD ["nginx", "-g", "daemon off;"]
    

    nginx.conf for app1

    include mime.types;
    include /etc/nginx/mime.types;
    server {
        listen       80;
        server_name  localhost;
        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
            try_files $uri $uri/ /index.html;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }
    }
    

    nginx.conf for app2

    include mime.types;
    include /etc/nginx/mime.types;
    server {
        listen       80;
        server_name  localhost;
        location /test {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
            try_files $uri $uri/ /test/index.html;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }
        location /.ico {
            root   /usr/share/nginx/html;
            add_header Content-Type     image/x-icon;
        }
    }