Multi Stage Docker builds with Angular and Nginx

This blog post shows multi stage Dockerfile that builds and deploys Angular app in Nginx container


###### Install dependencies only when needed ######
FROM node:16-alpine AS builder

# Make /app as working directory

# Copy package.json file
COPY package.json .

# Install dependencies
RUN npm install --legacy-peer-deps

# Copy the source code to the /app directory
COPY . .

# Build the application
RUN npm run build --  --output-path=dist --configuration=$CONFIGURATION --output-hashing=all

######  Use NgInx alpine image  ###### 
FROM nginx:stable-alpine

# Remove default nginx website
RUN rm -rf /usr/share/nginx/html/*

# Copy nginx config file
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf

# Copy dist folder fro build stage to nginx public folder
COPY --from=builder /app/dist /usr/share/nginx/html

# Start NgInx service
CMD ["nginx", "-g", "daemon off;"]

The above Dockerfile has 2 stages

Stage 1 – install NPM dependencies and builds Angular project

Stage 2 – Builds docker image from dist directory generated by previous stage

Stage 1: Install dependencies and Build Angular project

  1. We use Node 16 alpine image to build the project and it accepts CONFIGURATION build argument. You can override this during build based on your environment
docker build --build-arg CONFIGURATION=dev .

and you can also define as many arguments as you like

2. Then make/app as working directory. All of the source code and files will be copies to /app directory inside Node container


3. Copy the package.json file to /app directory. This will enable Docker to cache the node_modules rather than building from scratch and sub sequent builds use these when package.json file is unchanged.

COPY package.json .

4. Install dependencies using npm install command and specify flag — legacy-peer-deps to prevent build errors in NPM 7+

RUN npm install --legacy-peer-deps

5. Then copy the source code and build the project using npm run build

COPY . .RUN npm run build --  --output-path=dist --configuration=$CONFIGURATION --output-hashing=all

6. The built code will be present in /app/dist directory in Node container

Stage 2: Build Docker image

  1. We use NgInx alpine stable image to serve Angular application in production
  2. Remove existing HTML content using the command
RUN rm -rf /usr/share/nginx/html/*

3. Copy the Nginx config file from source to /etc/nginx/nginx.conf directory. If you don’t have one, you can use the below one

user nginx;
worker_processes auto;

error_log /var/log/nginx/error.log warn;
pid /var/run/;

  worker_connections 1024;

  upstream springbootapp
    server pres_springboot:8080;

    location /
      #### Gzip Settings  ####
      gzip on;
      gzip_min_length   1100;
      gzip_vary         on;
      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;
      gzip_comp_level   5;

      #### Serve Angular Application ####
      root /usr/share/nginx/html;
      try_files $uri $uri/ /index.html;
      add_header Cache-Control "no-store, no-cache, must-revalidate";
      proxy_http_version 1.1;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_set_header X-Forwarded-Port $server_port;

    location /api
      proxy_pass http://springbootapp;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For
      proxy_set_header X-Forwarded-Host $host;
      proxy_set_header X-Forwarded-Server $host;
      proxy_set_header X-Forwarded-Port $server_port;
      proxy_set_header X-Forwarded-Proto $scheme;

  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 30m;
  include /etc/nginx/conf.d/*.conf;

4. Then Copy dist folder from build stage to nginx public folder

COPY — from=builder /app/dist /usr/share/nginx/html

5. At the end specify the NgInx start command. That’s it.

You can also split Stage 1 into two separate stages. One to install dependencies and second one to build the Angular app 🙂

Pavan Kumar Jadda
Pavan Kumar Jadda
Articles: 36

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.