Naftis
Naftis

Reputation: 4559

Serving Angular 10 app in Docker via NGINX

I am trying to deploy an Angular 10 web app in Docker using NGINX, but I cannot get it to be served by it. Googling around I found a lot of sparse information, but I came up with this procedure:

  1. normally build the Angular app for production (--prod), outside Docker. The result gets saved into the dist folder of the project, which then will be copied into a container. I found other examples which use multi-stage build inside the container; but this is discouraged for scalability purposes. At any rate, building the app manually gives me easier control on the result, so to me this is the way to go.

  2. provide a Dockerfile using nginx:alpine, where I just copy a custom configuration and my compiled app to the container, and then run it as an executable via ENTRYPOINT:

FROM nginx:alpine

COPY nginx.conf /etc/nginx/nginx.conf

WORKDIR /usr/share/nginx/html
COPY dist/myapp/ .

ENTRYPOINT ["nginx", "-g", "daemon off;"]

This way, NGINX log should be redirected to the console, and thus shown in the container's terminal.

  1. the meat here is in the NGINX configuration file. I don't know much about it, but from the evidence at https://blog.codecentric.de/en/2019/03/docker-angular-dockerize-app-easily/ and https://denys.dev/2018-03-02/your-angular-apps-as-docker-containers/, I came up with this, which should correctly take HTML 5 routing into account (nginx.conf, saved in the same folder of the Dockerfile):
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    server {
        listen 80;
        server_name  localhost;

        root   /usr/share/nginx/html;
        index  index.html index.htm;
        include /etc/nginx/mime.types;

        gzip on;
        gzip_min_length 1000;
        gzip_proxied expired no-cache no-store private auth;
        gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

        location / {
            try_files $uri $uri/ /index.html;
        }
    }
}

(I also found this other post, but it has a less complete configuration for Angular's purposes).

  1. build the image (docker build . -t myrepo/myapp:tag). When doing so, it took a lot of time for sending useless content outside the dist folder. Thus, following a tip at https://denys.dev/2018-03-02/your-angular-apps-as-docker-containers/ ("optimizing the build"), I added a .dockerignore with:
e2e
node_modules
src

This time, the build was much faster. In both cases it was successfully built.

I can now run the container, either directly (docker run -d -p 4200:80 myrepo/myapp:tag), or from a docker compose script. In both cases, I will need to redirect the container's port 80 (NGINX) to the host port 4200 (the usual Angular app localhost port).

The problem: I'd expect to see my app served by NGINX at localhost:4200. Instead, what I find is just the default welcome NGINX page, telling me that the NGINX server is successfully installed and working, but further configuration is required.

I looked for something in the logs, but docker logs mycontainer displayed nothing.

I tried to inspect the container's filesystem to see if the Angular app is in its place (usr/share/nginx/html). To this end, I followed the advice at Exploring Docker container's file system: for the alpine image there is no bash, so I dumped the filesystem into a TAR file with docker export mycontainer > mydump.tar. By looking at the TAR's content I can see that my app files are there: index.html, JS files, assets, and the like.

What am I missing here to complete the NGINX configuration and get my app served?

UPDATE

I followed @Derek's directions: deleted .gitignore, replaced my nginx.conf with the suggested one, rebuilt the app, and run it. This time the build took about 15 minutes, and sent 1.134 GB of build context to the Docker daemon.

To quickly run it in my test machine I published the image in the Docker Hub. The whole project is open source, so if anyone wants to diagnose something in a repro environment there is both the binary image and its source:

Meantime I tried to create an empty dummy Angular app, and use my original Dockerfile and nginx files. To my surprise it worked. The only difference is that the dummy Angular app had no routing, whereas mine has it with hash location strategy. Supposing some hash-related config issue in NGINX, I tried to access mine from http://localhost:4200/#/home, with no luck: same welcome page.

Upvotes: 5

Views: 5892

Answers (2)

Naftis
Naftis

Reputation: 4559

I think I've found the problem: by looking at the filesystem dump, I noticed the default.conf file; inspecting its content, I saw that there already was a location for localhost at port 80. I was thinking that this should not be a problem, because my nginx.conf file did not import (include directive) it; but it seems that it still gets evaluated. At least, once I removed it in my Dockerfile:

FROM nginx:alpine
COPY nginx.conf /etc/nginx/nginx.conf
RUN rm /etc/nginx/conf.d/default.conf
WORKDIR /usr/share/nginx/html
COPY dist/apps/cadmus/ .
EXPOSE 80

the app was now launched as expected. Hope this can help newbies like me, defining a clear procedure for serving Angular (I suppose anyway I could reintroduce the gzip directive and even the .dockerignore)...

Upvotes: 2

Derek Menénedez
Derek Menénedez

Reputation: 2367

I put you how I do my container... I see all it's the same but please delete the .dockerignore... you don't need to ignore src... etc when you only copy the dist file...

IMPORTANT

  1. Verify the name of your app when you run ng build.... go to the folder and watch the name of the folder to verify it's my app

STRUCTURE

This need to be in the root of your app

src
dist
    name_of_your_app
        index.html //and the other files
e2e
node_modules
nginx.conf
Dockerfile

DOCKERFILE

FROM nginx:alpine
COPY nginx.conf /etc/nginx/nginx.conf
COPY dist/name_of_your_app/ /usr/share/nginx/html
EXPOSE 80

NGINX CONF

user  nginx;
worker_processes  auto;

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;
        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;
        }
    }
}

build

docker build -t my_app .

run

docker run -d -p 4200:80 -t my_app

And that's all... maybe somem configuration, tags ... dockerignore it's your problem. Tell me if that solve your problem.

Upvotes: 3

Related Questions