สร้าง Docker สำหรับ Angular ด้วย Node.js และ Nginx

Oct. 4, 2024 · boychawin

ในบทความนี้ เราจะสร้าง Docker image สำหรับการ deploy แอป Angular ด้วย Node.js และ Nginx โดยมีการแบ่งออกเป็นสองขั้นตอนหลัก คือ การ build แอปด้วย Node.js และการตั้งค่า Nginx สำหรับการ serve แอป โดยในตัวอย่างนี้จะทำงานบน Alpine Linux เพื่อลดขนาดของ image ให้เล็กที่สุด

โครงสร้าง Dockerfile และการตั้งค่า Nginx

Dockerfile ของเราจะแบ่งออกเป็น 2 ขั้นตอน (multi-stage build)

  1. ขั้นตอนแรก (Builder): ทำการ build แอป Angular ของเรา
  2. ขั้นตอนที่สอง (Nginx): ใช้ Nginx เพื่อ serve แอปที่ build เสร็จแล้ว

ตัวอย่างโค้ด

# Dockerfile

# Stage 1: Build the app with Node.js
FROM node:18-alpine AS builder

# Copy the built app (ensure dist/myapp/browser exists)
COPY dist/myapp/browser /usr/src/app/build

# Stage 2: Set up Nginx
FROM nginx:stable-alpine

# Install timezone data and set timezone
RUN apk update && apk add --no-cache tzdata && rm -rf /var/cache/apk/*
ENV TZ="Asia/Bangkok"

# Set the working directory
WORKDIR /usr/src/app

# Copy the Nginx configuration file
COPY nginx.conf /etc/nginx/nginx.conf

# Copy built files from the build stage to the Nginx HTML folder
COPY --from=builder /usr/src/app/build /usr/share/nginx/html

# Set permissions for Nginx to avoid permission issues
RUN chown -R nginx:nginx /usr/share/nginx && chmod -R 755 /usr/share/nginx && \
    chown -R nginx:nginx /var/cache/nginx && \
    chown -R nginx:nginx /var/log/nginx && \
    chown -R nginx:nginx /etc/nginx && \
    touch /var/run/nginx.pid && chown -R nginx:nginx /var/run/nginx.pid

# Use Nginx user for the running process
USER nginx

# Run Nginx in the foreground (no daemon mode)
CMD ["nginx", "-g", "daemon off;"]

การตั้งค่า Nginx (nginx.conf)

ไฟล์การตั้งค่า Nginx ที่กำหนดค่าต่างๆ เพื่อให้สามารถ serve แอปได้อย่างมีประสิทธิภาพ และเพิ่มการรักษาความปลอดภัยพื้นฐานด้วยการเพิ่ม HTTP headers เช่น X-Frame-Options, X-XSS-Protection, และ Content-Security-Policy.

# nginx.conf

user nginx;
worker_processes 2;

worker_rlimit_nofile 10000;

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

http {
  include       /etc/nginx/mime.types;
  default_type  application/octet-stream;

  open_file_cache max=3000 inactive=120s;
  open_file_cache_valid 45s;
  open_file_cache_min_uses 2;
  open_file_cache_errors off;

  add_header Cache-Control 'public, no-cache';

  tcp_nodelay off;

  access_log off;   # Disable logs for testing, enable in production with: access_log /var/log/nginx/access.log;
  error_log off;    # Disable error logs for testing, enable in production with: error_log /var/log/nginx/error.log warn;

  sendfile on;
  tcp_nopush on;

  # Reduce the data sent over network -- for testing environment
  gzip on;
  # gzip_static on;  # Uncomment if your build process pre-compresses assets
  gzip_min_length 10240;
  gzip_comp_level 1;
  gzip_vary on;
  gzip_disable "msie6";
  gzip_proxied expired no-cache no-store private auth;
  gzip_types
      text/css
      text/javascript
      text/xml
      text/plain
      text/x-component
      application/javascript
      application/x-javascript
      application/json
      application/xml
      application/rss+xml
      application/atom+xml
      font/truetype
      font/opentype
      application/vnd.ms-fontobject
      image/svg+xml;

  # Free up memory by closing connections on non-responding clients
  reset_timedout_connection on;

  # Request timeout (default 60s)
  client_body_timeout 10;

  # If client stops responding, free up memory (default 60s)
  send_timeout 2;

  keepalive_timeout 30;
  keepalive_requests 100000;  # High for testing environment, reduce for production

  client_header_buffer_size 16k;
  large_client_header_buffers 4 16k;

  server {
      listen 8080;

      root   /usr/share/nginx/html;
      index index.html;

      server_tokens off;

      location / {
        add_header X-Frame-Options "SAMEORIGIN";
        add_header X-XSS-Protection "1; mode=block";
        add_header X-Content-Type-Options nosniff;
        add_header Content-Security-Policy "default-src * 'self' wss:; img-src * 'self' data: https:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; frame-ancestors 'self';";
        add_header Strict-Transport-Security 'max-age=15778476; includeSubDomains; preload';
        add_header Cache-Control 'no-cache';
        add_header Referrer-Policy "no-referrer";
      }

      location /test/ {
        alias /usr/share/nginx/html/;
        try_files $uri $uri/ /index.html;
      }
  }
}

ขั้นตอนการใช้งาน

  1. Build image โดยใช้คำสั่ง:

    docker build -t myapp .

  1. Run container โดย expose port 8080 และ map ไปยัง port 80 ใน container

    docker run -d --name myapp_container -p 8080:80 myapp

  1. ตรวจสอบสถานะของ container

      docker ps

สรุป

การใช้ Docker และ Nginx สำหรับการ deploy แอป Angular ช่วยให้เราสามารถปรับแต่งการทำงานของเซิร์ฟเวอร์ให้เหมาะสมกับสภาพแวดล้อม โดยไฟล์ Dockerfile และ nginx.conf ในบทความนี้ถูกออกแบบมาเพื่อใช้งานจริงกับแอป Angular ที่สร้างด้วย Node.js