Skip to content

11. Docker Multi-stage builds


1. 테스트 구성 환경

1) 웹사이트 배포 워크 플로우

001


2) Angular

  • 로컬 및 리모트 환경에서 Angular를 이용하여 웹사이트 개발
  • Angular를 이용하면 수많은 HTML, CSS, TypeScript 파일들이 생성됨
  • 이외의 파일들은 Angular 프로젝트에서 사용되는 설정 파일, 디펜던시 파일 등이 있음
  • 디펜던시 파일은 node_modules라는 디렉터리에 저장되어 있고, 프로젝트에 따라 용량이 매우 커질 수 있음
  • node_modules 디렉터리는 .dockerignore에 지정하여 제외시킨 후 빌드 시 설치함


3) Docker Node.js

  • Angular 프로젝트가 완성되었으면 Docker의 Node.js 이미지를 사용하여 빌드해야 함
  • 여기서 빌드란 HTML, CSS, TypeScript 파일들을 압축하는 개념으로, 수많은 파일들이 몇 개의 파일들로 줄어듦
  • 또한 TypeScript 파일은 JavaScript 파일로 컴파일되어 웹 브라우저가 로직을 읽을 수 있게 됨
  • 결과적으로 index.html, *.css, *.js 형태의 파일이 생성됨


4) Docker Nginx

  • 웹 브라우저는 index.html을 요청할 곳이 필요하며, 웹 서버가 index.html을 응답함
  • Angular는 Single Page Application(index.html 하나로 Navigating 할 수 있는 애플리케이션)이므로 nginx.conf 파일만 수정하면 웹 서버(현재 테스트에서는 프론트엔드 서버)를 구축할 수 있음


5) Docker Compose

  • 프론트엔드 서버뿐만 아니라 백엔드 서버, 데이터베이스 서버 등의 네트워크 구성 및 관리를 위해 Docker Compose를 사용함
  • 현재 테스트에서는 프론트엔드 서버만 구성함


6) 테스트용 디렉터리 생성 및 진입

mkdir -p ./Angular-Web/Web && cd ./Angular-Web


2. Angular (디폴트 프로젝트로 테스트)

1) NVM 설치

export NVM_DIR="$HOME/.nvm" && (
  git clone https://github.com/nvm-sh/nvm.git "$NVM_DIR"
  cd "$NVM_DIR"
  git checkout `git describe --abbrev=0 --tags --match "v[0-9]*" $(git rev-list --tags --max-count=1)`
  ) && \. "$NVM_DIR/nvm.sh"
cat >>${HOME}/.bashrc <<EOF

# NVM
export NVM_DIR="\$HOME/.nvm"
# This loads nvm
[ -s "\$NVM_DIR/nvm.sh" ] && \. "\$NVM_DIR/nvm.sh"
# This loads nvm bash_completion
[ -s "\$NVM_DIR/bash_completion" ] && \. "\$NVM_DIR/bash_completion"
EOF
source $HOME/.bashrc


2) Node.js 설치

nvm install --lts && \
  nvm alias default lts/* && \
  nvm use lts/*


3) Angular 설치 및 프로젝트 생성

npm install -g @angular/cli
ng new angular


3. Nginx

1) Angular 프로젝트 디펜던시 제외

echo "**/node_modules" >./Web/.dockerignore


2) nginx.conf 파일 작성

vim ./Web/nginx.conf
user www-data;
worker_processes 1;
pid /run/nginx.pid;

events {
        worker_connections 4096;
        multi_accept on;
        use epoll;
}

http {
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;

        server {
                listen 80;
                server_name localhost;

                location / {
                        root   /usr/share/nginx/html;
                        index  index.html index.htm;

                        try_files $uri $uri/ /index.html?$args;
                }

                error_page 500 502 503 504 /50x.html;
                location = /50x.html {
                        root /usr/share/nginx/html;
                }
        }

        client_body_timeout 86400;
        send_timeout 86400;
        reset_timedout_connection off;

        proxy_connect_timeout 86400;
        proxy_send_timeout 86400;
        proxy_read_timeout 86400;

        types_hash_max_size 2048;
        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
        ssl_prefer_server_ciphers on;

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        gzip on;
}


3) Dockerfile 파일 작성

vim ./Web/Dockerfile
# Get Arg from docker-compose.yml
ARG PROJECT

# Build Angular as base
FROM node:16.15.0-alpine AS base
ARG PROJECT
WORKDIR /app
COPY ./$PROJECT .
RUN npm install && npm run build

# Build Nginx as prod from base(Angular)
FROM nginx:latest AS prod
ARG PROJECT
RUN rm -rf /usr/share/nginx/html
COPY --from=base /app/dist/$PROJECT /usr/share/nginx/html


4. Docker Compose

1) .env 파일 작성

vim ./.env
DRIVER="bridge"
SUBNET="172.18.0.0/16"
GATEWAY="172.18.0.1"

WEB_PROJECT="angular"
WEB_DIR="./Web"
WEB_DOCKERFILE="${WEB_DIR}/Dockerfile"
WEB_CONFIGFILE="${WEB_DIR}/nginx.conf"
WEB_IPV4="172.18.0.2"
WEB_HPORT="9999"
WEB_CPORT="80"


2) docker-compose.yml 파일 작성

vim ./docker-compose.yml
version: "3.8"

services:
  web:
    container_name: "web"
    build:
      context: .
      dockerfile: ${WEB_DOCKERFILE}
      target: prod
      args:
        - PROJECT=${WEB_PROJECT}
    ports:
      - ${WEB_HPORT}:${WEB_CPORT}
    working_dir: /
    volumes:
      - ${WEB_CONFIGFILE}:/etc/nginx/nginx.conf
    networks:
      net:
        ipv4_address: ${WEB_IPV4}

networks:
  net:
    driver: ${DRIVER}
    ipam:
      config:
        - subnet: ${SUBNET}
          gateway: ${GATEWAY}


3) Docker Compose 실행

sudo docker-compose -p angular_web up -d


  • Docker Compose를 실행하면 다음과 같이 angular_web_web 이미지를 이용한 web 컨테이너가 생성되었음


002


  • Dockerfile에서 Multi-stage builds를 이용했기 때문에 중간 이미지인 <none>과 최종 이미지 angular_web_web이 생성되었음
  • 중간 이미지인 <none>은 Angular 프로젝트를 빌드하는 것과 관련이 있을 뿐이지 웹사이트를 배포하는 것과는 관련이 없음
  • 현재 기준으로 중간 이미지 자동 제거 기능은 없지만 Bash 스크립트로 해결 가능함


003


4) <none> 중간 이미지 제거

  • <none>과 같이 컨테이너에 이용되지 않은 이미지는 다음과 같이 제거할 수 있음


sudo docker image prune -f


  • 결과적으로, Multi-stage builds를 이용하면 총 539MB가 필요한 웹사이트 배포를 142MB에 할 수 있음(nginx:latestnode:16.15.0-alpine 이미지 제외 - 새로 빌드된 이미지 및 컨테이너에 종속되기 때문임)
  • 142MB인 이유는 nginx:latest 이미지의 사이즈(거의 전부) + Angular 빌드 후 사이즈이기 때문임


004