Docker-ΠΎΠ±Ρ€Π°Π· для Ρ€Π°Π·Π΄Π°Ρ‡ΠΈ Single Page Application

Single-page Application (SPA) – это Π½Π°Π±ΠΎΡ€ статичСских JavaScript ΠΈ HTML Ρ„Π°ΠΉΠ»ΠΎΠ², Π° Ρ‚Π°ΠΊ ΠΆΠ΅ ΠΊΠ°Ρ€Ρ‚ΠΈΠ½ΠΎΠΊ ΠΈ Π΄Ρ€ΡƒΠ³ΠΈΡ… рСсурсов. ΠŸΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ ΠΎΠ½ΠΈ Π½Π΅ ΠΈΠ·ΠΌΠ΅Π½ΡΡŽΡ‚ΡΡ динамичСски, ΠΎΠΏΡƒΠ±Π»ΠΈΠΊΠΎΠ²Π°Ρ‚ΡŒ ΠΈΡ… Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ ΠΎΡ‡Π΅Π½ΡŒ просто. Для этого сущСствуСт большоС количСство Π΄Π΅ΡˆΡ‘Π²Ρ‹Ρ… ΠΈ Π΄Π°ΠΆΠ΅ бСсплатных сСрвисов, начиная с простого GitHub Pages (Π° для ΠΊΠΎΠ³ΠΎ-Ρ‚ΠΎ Π΄Π°ΠΆΠ΅ с narod.ru) ΠΈ заканчивая CDN Π²Ρ€ΠΎΠ΄Π΅ Amazon S3. Однако ΠΌΠ½Π΅ Π½ΡƒΠΆΠ½ΠΎ Π±Ρ‹Π»ΠΎ Π΄Ρ€ΡƒΠ³ΠΎΠ΅.

МнС Π½ΡƒΠΆΠ΅Π½ Π±Ρ‹Π» Docker-ΠΎΠ±Ρ€Π°Π· с SPA, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π΅Π³ΠΎ Π»Π΅Π³ΠΊΠΎ ΠΌΠΎΠΆΠ½ΠΎ Π±Ρ‹Π»ΠΎ Π·Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΊΠ°ΠΊ Π² ΠΏΡ€ΠΎΠ΄Π°ΠΊΡˆΠ΅Π½Π΅ Π² составС Kubernetes-кластСра, Ρ‚Π°ΠΊ ΠΈ Π½Π° машинС back-end Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ°, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ понятия Π½Π΅ ΠΈΠΌΠ΅Π΅Ρ‚, Ρ‡Ρ‚ΠΎ Ρ‚Π°ΠΊΠΎΠ΅ SPA.

Π― для сСбя ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΠ» ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠ΅ трСбования ΠΊ ΠΎΠ±Ρ€Π°Π·Ρƒ:

  • простота Π² использовании (Π½ΠΎ Π½Π΅ Π² сборкС);
  • ΠΌΠΈΠ½ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ Ρ€Π°Π·ΠΌΠ΅Ρ€ ΠΊΠ°ΠΊ с Ρ‚ΠΎΡ‡ΠΊΠΈ зрСния диска, Ρ‚Π°ΠΊ ΠΈ с Ρ‚ΠΎΡ‡ΠΊΠΈ зрСния RAM;
  • настройка Ρ‡Π΅Ρ€Π΅Π· ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ окруТСния, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΎΠ±Ρ€Π°Π· ΠΌΠΎΠΆΠ½ΠΎ Π±Ρ‹Π»ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π² Ρ€Π°Π·Π½Ρ‹Ρ… срСдах;
  • максимально эффСктивная Ρ€Π°Π·Π΄Π°Ρ‡Π° Ρ„Π°ΠΉΠ»ΠΎΠ².

БСгодня я расскаТу ΠΊΠ°ΠΊ:

  • Π²Ρ‹ΠΏΠΎΡ‚Ρ€ΠΎΡˆΠΈΡ‚ΡŒ nginx;
  • ΡΠΎΠ±Ρ€Π°Ρ‚ΡŒ brotli ΠΈΠ· исходников;
  • Π½Π°ΡƒΡ‡ΠΈΡ‚ΡŒ статичСскиС Ρ„Π°ΠΉΠ»Ρ‹ ΠΏΠΎΠ½ΠΈΠΌΠ°Ρ‚ΡŒ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ окруТСния;
  • Π½Ρƒ ΠΈ ΠΊΠΎΠ½Π΅Ρ‡Π½ΠΎ ΠΊΠ°ΠΊ ΡΠΎΠ±Ρ€Π°Ρ‚ΡŒ ΠΈΠ· всСго этого Docker-ΠΎΠ±Ρ€Π°Π·.

ЦСль этой ΡΡ‚Π°Ρ‚ΡŒΠΈ ΠΏΠΎΠ΄Π΅Π»ΠΈΡ‚ΡŒΡΡ ΠΌΠΎΠΈΠΌ ΠΎΠΏΡ‹Ρ‚ΠΎΠΌ ΠΈ ΡΠΏΡ€ΠΎΠ²ΠΎΡ†ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΎΠΏΡ‹Ρ‚Π½Ρ‹Ρ… участников сообщСства Π½Π° ΠΊΠΎΠ½ΡΡ‚Ρ€ΡƒΠΊΡ‚ΠΈΠ²Π½ΡƒΡŽ ΠΊΡ€ΠΈΡ‚ΠΈΠΊΡƒ.

Π‘Π±ΠΎΡ€ΠΊΠ° ΠΎΠ±Ρ€Π°Π·Π° для сборки

Π§Ρ‚ΠΎΠ±Ρ‹ Ρ„ΠΈΠ½Π°Π»ΡŒΠ½Ρ‹ΠΉ Docker-ΠΎΠ±Ρ€Π°Π· получился малСньким ΠΏΠΎ Ρ€Π°Π·ΠΌΠ΅Ρ€Ρƒ, Π½ΡƒΠΆΠ½ΠΎ ΠΏΡ€ΠΈΠ΄Π΅Ρ€ΠΆΠΈΠ²Π°Ρ‚ΡŒΡΡ Π΄Π²ΡƒΡ… ΠΏΡ€Π°Π²ΠΈΠ»: ΠΌΠΈΠ½ΠΈΠΌΡƒΠΌ слоёв ΠΈ минималистичный Π±Π°Π·ΠΎΠ²Ρ‹ΠΉ ΠΎΠ±Ρ€Π°Π·. Одним ΠΈΠ· самых ΠΌΠ°Π»Π΅Π½ΡŒΠΊΠΈΡ… Π±Π°Π·ΠΎΠ²Ρ‹Ρ… ΠΎΠ±Ρ€Π°Π·ΠΎΠ² являСтся ΠΎΠ±Ρ€Π°Π· Alpine Linux, поэтому ΠΈΠΌΠ΅Π½Π½ΠΎ Π΅Π³ΠΎ я ΠΈ Π²Ρ‹Π±Π΅Ρ€Ρƒ. ΠšΡ‚ΠΎ-Ρ‚ΠΎ ΠΌΠΎΠΆΠ΅Ρ‚ Π²ΠΎΠ·Ρ€Π°Π·ΠΈΡ‚ΡŒ, Ρ‡Ρ‚ΠΎ Alpine Π½Π΅ ΠΏΠΎΠ΄Ρ…ΠΎΠ΄ΠΈΡ‚ для ΠΏΡ€ΠΎΠ΄Π°ΠΊΡˆΠ΅Π½Π° ΠΈ, Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ, окаТСтся ΠΏΡ€Π°Π². Но Π»ΠΈΡ‡Π½ΠΎ Ρƒ мСня с Π½ΠΈΠΌ Π½ΠΈΠΊΠΎΠ³Π΄Π° Π½Π΅ Π²ΠΎΠ·Π½ΠΈΠΊΠ°Π»ΠΎ ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌ ΠΈ Π½ΠΈΠΊΠ°ΠΊΠΈΡ… Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ² ΠΏΡ€ΠΎΡ‚ΠΈΠ² Π½Π΅Π³ΠΎ Π½Π΅Ρ‚.

Π§Ρ‚ΠΎΠ±Ρ‹ Π±Ρ‹Π»ΠΎ помСньшС слоёв, я Π±ΡƒΠ΄Ρƒ ΡΠΎΠ±ΠΈΡ€Π°Ρ‚ΡŒ ΠΎΠ±Ρ€Π°Π· Π² 2 этапа. ΠŸΠ΅Ρ€Π²Ρ‹ΠΉ – Ρ‡Π΅Ρ€Π½ΠΎΠ²ΠΎΠΉ, Π² Π½Ρ‘ΠΌ останутся всС Π²ΡΠΏΠΎΠΌΠΎΠ³Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹ ΠΈ Π²Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹. А Π² чистовой я Π·Π°ΠΏΠΈΡˆΡƒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ρ„ΠΈΠ½Π°Π»ΡŒΠ½ΡƒΡŽ Π²Π΅Ρ€ΡΠΈΡŽ прилоТСния.

Начнём со Π²ΡΠΏΠΎΠΌΠΎΠ³Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΠ³ΠΎ ΠΎΠ±Ρ€Π°Π·Π°.

Для Ρ‚ΠΎΠ³ΠΎ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡΠΊΠΎΠΌΠΏΠΈΠ»ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ SPA-ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅, ΠΎΠ±Ρ‹Ρ‡Π½ΠΎ, Π½ΡƒΠΆΠ΅Π½ node.js. Π― Π²ΠΎΠ·ΡŒΠΌΡƒ ΠΎΡ„ΠΈΡ†ΠΈΠ°Π»ΡŒΠ½Ρ‹ΠΉ ΠΎΠ±Ρ€Π°Π· Π² ΠΊΠΎΠΌΠΏΠ»Π΅ΠΊΡ‚Π΅ с ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΌ Ρ‚Π°ΠΊ ΠΆΠ΅ Π΅ΡΡ‚ΡŒ ΠΏΠ°ΠΊΠ΅Ρ‚Π½Ρ‹Π΅ ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€Ρ‹ npm ΠΈ yarn. ΠžΡ‚ сСбя я добавлю node-gyp, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π½ΡƒΠΆΠ΅Π½ для сборки Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… npm-ΠΏΠ°ΠΊΠ΅Ρ‚ΠΎΠ², ΠΈ компрСссор Brotli ΠΎΡ‚ Google, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ пригодится Π½Π°ΠΌ ΠΏΠΎΠ·ΠΆΠ΅.

Dockerfile с коммСнтариями.

# Π‘Π°Π·ΠΎΠ²Ρ‹ΠΉ ΠΎΠ±Ρ€Π°Π·
FROM node:12-alpine
LABEL maintainer="Aleksey Maydokin <[email protected]>"
ENV BROTLI_VERSION 1.0.7
# ΠŸΠ°ΠΊΠ΅Ρ‚Ρ‹, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π½ΡƒΠΆΠ½Ρ‹, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡΠΎΠ±Ρ€Π°Ρ‚ΡŒ ΠΈΠ· исходников Brotli
RUN apk add --no-cache --virtual .build-deps 
        bash 
        gcc 
        libc-dev 
        make 
        linux-headers 
        cmake 
        curl 
    && mkdir -p /usr/src 
    # Π˜ΡΡ…ΠΎΠ΄Π½ΠΈΠΊΠΈ Brotli скачиваСм ΠΈΠ· ΠΎΡ„ΠΈΡ†ΠΈΠ°Π»ΡŒΠ½ΠΎΠ³ΠΎ рСпозитория
    && curl -LSs https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz | tar xzf - -C /usr/src 
    && cd /usr/src/brotli-$BROTLI_VERSION 
    # ΠšΠΎΠΌΠΏΠΈΠ»ΠΈΡ€ΡƒΠ΅ΠΌ Brotli
    && ./configure-cmake --disable-debug && make -j$(getconf _NPROCESSORS_ONLN) && make install 
    # ДобавляСм node-gyp
    && yarn global add node-gyp 
    # Π£Π±ΠΈΡ€Π°Π΅ΠΌ Π·Π° собой мусор
    && apk del .build-deps && yarn cache clean && rm -rf /usr/src

Π£ΠΆΠ΅ здСсь я Π±ΠΎΡ€ΡŽΡΡŒ Π·Π° ΠΌΠΈΠ½ΠΈΠΌΠ°Π»ΠΈΠ·ΠΌ, поэтому ΠΎΠ±Ρ€Π°Π· собираСтся ΠΎΠ΄Π½ΠΎΠΉ большой ΠΊΠΎΠΌΠ°Π½Π΄ΠΎΠΉ.

Π“ΠΎΡ‚ΠΎΠ²Ρ‹ΠΉ ΠΎΠ±Ρ€Π°Π· ΠΌΠΎΠΆΠ½ΠΎ Π½Π°ΠΉΡ‚ΠΈ Ρ‚ΡƒΡ‚: https://hub.docker.com/r/alexxxnf/spa-builder. Π₯отя я Ρ€Π΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡƒΡŽ Π½Π΅ ΠΏΠΎΠ»Π°Π³Π°Ρ‚ΡŒΡΡ Π½Π° Ρ‡ΡƒΠΆΠΈΠ΅ ΠΎΠ±Ρ€Π°Π·Ρ‹ ΠΈ ΡΠΎΠ±Ρ€Π°Ρ‚ΡŒ свой.

nginx

Для Ρ€Π°Π·Π΄Π°Ρ‡ΠΈ статики ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ любой web-сСрвСр. Π― ΠΏΡ€ΠΈΠ²Ρ‹ΠΊ Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ с nginx, поэтому ΠΈ сСйчас Π±ΡƒΠ΄Ρƒ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π΅Π³ΠΎ.

Π£ nginx Π΅ΡΡ‚ΡŒ ΠΎΡ„ΠΈΡ†ΠΈΠ°Π»ΡŒΠ½Ρ‹ΠΉ Docker-ΠΎΠ±Ρ€Π°Π·, ΠΎΠ΄Π½Π°ΠΊΠΎ для простой Ρ€Π°Π·Π΄Π°Ρ‡ΠΈ статики Π² Π½Ρ‘ΠΌ слишком ΠΌΠ½ΠΎΠ³ΠΎ ΠΌΠΎΠ΄ΡƒΠ»Π΅ΠΉ. КакиС ΠΈΠΌΠ΅Π½Π½ΠΎ Π²ΠΊΠ»ΡŽΡ‡Π΅Π½Ρ‹ Π² поставку ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ ΡΠΏΠ΅Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎΠΉ ΠΊΠΎΠΌΠ°Π½Π΄ΠΎΠΉ ΠΈΠ»ΠΈ ΠΆΠ΅ Π² ΠΎΡ„ΠΈΡ†ΠΈΠ°Π»ΡŒΠ½ΠΎΠΌ Dockerfile.

$ docker run —rm nginx:1-alpine nginx -V

nginx version: nginx/1.17.9
built by gcc 8.3.0 (Alpine 8.3.0) 
built with OpenSSL 1.1.1d  10 Sep 2019
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --with-perl_modules_path=/usr/lib/perl5/vendor_perl --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-Os -fomit-frame-pointer' --with-ld-opt=-Wl,--as-needed

Π― Π²ΠΎΠ·ΡŒΠΌΡƒ Π·Π° основу Dockerfile, Π½ΠΎ ΠΎΡΡ‚Π°Π²Π»ΡŽ Π² Π½Ρ‘ΠΌ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ρ‚ΠΎ, Ρ‡Ρ‚ΠΎ Π½ΡƒΠΆΠ½ΠΎ для Ρ€Π°Π·Π΄Π°Ρ‡ΠΈ статики. Мой Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ Π½Π΅ смоТСт Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ ΠΏΠΎ HTTPS, Π½Π΅ Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Ρ‚ΡŒ Π°Π²Ρ‚ΠΎΡ€ΠΈΠ·Π°Ρ†ΠΈΡŽ ΠΈ ΠΌΠ½ΠΎΠ³ΠΎΠ΅ Π΄Ρ€ΡƒΠ³ΠΎΠ΅. Π—Π°Ρ‚ΠΎ моя вСрсия смоТСт Ρ€Π°Π·Π΄Π°Π²Π°ΠΉ Ρ„Π°ΠΉΠ»Ρ‹, сТатыС Π°Π»Π³ΠΎΡ€ΠΈΡ‚ΠΌΠΎΠΌ Brotli, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ эффСктивнСС, Ρ‡Π΅ΠΌ gzip. Π‘ΠΆΠΈΠΌΠ°Ρ‚ΡŒ Ρ„Π°ΠΉΠ»Ρ‹ Π±ΡƒΠ΄Π΅ΠΌ ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π·, Π΄Π΅Π»Π°Ρ‚ΡŒ это Π½Π° Π»Π΅Ρ‚Ρƒ Π½Π΅Ρ‚ нСобходимости.

Π’ΠΎΡ‚ ΠΊΠ°ΠΊΠΎΠΉ Dockerfile Ρƒ мСня получился. ΠšΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ Π½Π° русском – ΠΌΠΎΠΈ, Π½Π° английском – ΠΈΠ· ΠΎΡ€ΠΈΠ³ΠΈΠ½Π°Π»Π°.

Dockerfile

# Π‘Π°Π·ΠΎΠ²Ρ‹ΠΉ ΠΎΠ±Ρ€Π°Π· снова Alpine
FROM alpine:3.9
LABEL maintainer="Aleksey Maydokin <[email protected]>"
ENV NGINX_VERSION 1.16.0
ENV NGX_BROTLI_VERSION 0.1.2
ENV BROTLI_VERSION 1.0.7
RUN set -x 
    && addgroup -S nginx 
    && adduser -D -S -h /var/cache/nginx -s /sbin/nologin -G nginx nginx 
# УстанавливаСм ΠΏΠ°ΠΊΠ΅Ρ‚Ρ‹, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π½ΡƒΠΆΠ½Ρ‹ Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡΠΎΠ±Ρ€Π°Ρ‚ΡŒ nginx ΠΈ ΠΌΠΎΠ΄ΡƒΠ»ΡŒ ngx_brotli ΠΊ Π½Π΅ΠΌΡƒ
    && apk add --no-cache --virtual .build-deps 
            gcc 
            libc-dev 
            make 
            linux-headers 
            curl 
    && mkdir -p /usr/src 
# Π‘ΠΊΠ°Ρ‡ΠΈΠ²Π°Π΅ΠΌ исходники
    && curl -LSs https://nginx.org/download/nginx-$NGINX_VERSION.tar.gz | tar xzf - -C /usr/src 
    && curl -LSs https://github.com/eustas/ngx_brotli/archive/v$NGX_BROTLI_VERSION.tar.gz | tar xzf - -C /usr/src 
    && curl -LSs https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz | tar xzf - -C /usr/src 
    && rm -rf /usr/src/ngx_brotli-$NGX_BROTLI_VERSION/deps/brotli/ 
    && ln -s /usr/src/brotli-$BROTLI_VERSION /usr/src/ngx_brotli-$NGX_BROTLI_VERSION/deps/brotli 
    && cd /usr/src/nginx-$NGINX_VERSION 
    && CNF="
            --prefix=/etc/nginx 
            --sbin-path=/usr/sbin/nginx 
            --modules-path=/usr/lib/nginx/modules 
            --conf-path=/etc/nginx/nginx.conf 
            --error-log-path=/var/log/nginx/error.log 
            --http-log-path=/var/log/nginx/access.log 
            --pid-path=/var/run/nginx.pid 
            --lock-path=/var/run/nginx.lock 
            --http-client-body-temp-path=/var/cache/nginx/client_temp 
            --http-proxy-temp-path=/var/cache/nginx/proxy_temp 
            --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp 
            --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp 
            --http-scgi-temp-path=/var/cache/nginx/scgi_temp 
            --user=nginx 
            --group=nginx 
            --without-http_ssi_module 
            --without-http_userid_module 
            --without-http_access_module 
            --without-http_auth_basic_module 
            --without-http_mirror_module 
            --without-http_autoindex_module 
            --without-http_geo_module 
            --without-http_split_clients_module 
            --without-http_referer_module 
            --without-http_rewrite_module 
            --without-http_proxy_module 
            --without-http_fastcgi_module 
            --without-http_uwsgi_module 
            --without-http_scgi_module 
            --without-http_grpc_module 
            --without-http_memcached_module 
            --without-http_limit_conn_module 
            --without-http_limit_req_module 
            --without-http_empty_gif_module 
            --without-http_browser_module 
            --without-http_upstream_hash_module 
            --without-http_upstream_ip_hash_module 
            --without-http_upstream_least_conn_module 
            --without-http_upstream_keepalive_module 
            --without-http_upstream_zone_module 
            --without-http_gzip_module 
            --with-http_gzip_static_module 
            --with-threads 
            --with-compat 
            --with-file-aio 
            --add-dynamic-module=/usr/src/ngx_brotli-$NGX_BROTLI_VERSION 
    " 
# Π‘ΠΎΠ±ΠΈΡ€Π°Π΅ΠΌ
    && ./configure $CNF 
    && make -j$(getconf _NPROCESSORS_ONLN) 
    && make install 
    && rm -rf /usr/src/ 
# УдаляСм динамичСский brotli ΠΌΠΎΠ΄ΡƒΠ»ΡŒ, оставляя Ρ‚ΠΎΠ»ΡŒΠΊΠΎ статичСский
    && rm /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so 
    && sed -i '$ d' /etc/apk/repositories 
# Bring in gettext so we can get `envsubst`, then throw
# the rest away. To do this, we need to install `gettext`
# then move `envsubst` out of the way so `gettext` can
# be deleted completely, then move `envsubst` back.
    && apk add --no-cache --virtual .gettext gettext 
    && mv /usr/bin/envsubst /tmp/ 
    && runDeps="$( 
        scanelf --needed --nobanner /usr/sbin/nginx /usr/lib/nginx/modules/*.so /tmp/envsubst 
            | awk '{ gsub(/,/, "nso:", $2); print "so:" $2 }' 
            | sort -u 
            | xargs -r apk info --installed 
            | sort -u 
    )" 
    && apk add --no-cache $runDeps 
    && apk del .build-deps 
    && apk del .gettext 
    && mv /tmp/envsubst /usr/local/bin/ 
# Bring in tzdata so users could set the timezones through the environment
# variables
    && apk add --no-cache tzdata 
# forward request and error logs to docker log collector
    && ln -sf /dev/stdout /var/log/nginx/access.log 
    && ln -sf /dev/stderr /var/log/nginx/error.log
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
STOPSIGNAL SIGTERM
CMD ["nginx", "-g", "daemon off;"]

Π― сразу ΠΆΠ΅ ΠΏΠΎΠΏΡ€Π°Π²Π»ΡŽ nginx.conf, Ρ‡Ρ‚ΠΎΠ±Ρ‹ gzip ΠΈ brotli Π±Ρ‹Π»ΠΈ Π²ΠΊΠ»ΡŽΡ‡Π΅Π½Ρ‹ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ. Π’Π°ΠΊ ΠΆΠ΅ Π²ΠΊΠ»ΡŽΡ‡Ρƒ ΠΊΡΡˆΠΈΡ€ΡƒΡŽΡ‰ΠΈΠ΅ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ, вСдь Ρƒ нас Π±ΡƒΠ΄Π΅Ρ‚ Ρ€Π°Π·Π΄Π°Π²Π°Ρ‚ΡŒΡΡ Π½ΠΈΠΊΠΎΠ³Π΄Π° Π½Π΅ ΠΌΠ΅Π½ΡΡŽΡ‰Π°ΡΡΡ статика. И послСдним ΡˆΡ‚Ρ€ΠΈΡ…ΠΎΠΌ Π±ΡƒΠ΄Π΅Ρ‚ пСрСадрСсация всСх 404 запросов Π½Π° index.html, это Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ для Π½Π°Π²ΠΈΠ³Π°Ρ†ΠΈΠΈ Π² SPA.

nginx.conf

user nginx;
worker_processes  1;
error_log /var/log/nginx/error.log warn;
pid       /var/run/nginx.pid;
load_module /usr/lib/nginx/modules/ngx_http_brotli_static_module.so;
events {
    worker_connections 1024;
}
http {
    include      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;
    gzip_static   on;
    brotli_static on;
    server {
        listen      80;
        server_name localhost;
        charset utf-8;
        location / {
            root html;
            try_files $uri /index.html;
            etag on;
            expires max;
            add_header Cache-Control public;
            location = /index.html {
                expires 0;
                add_header Cache-Control "no-cache, public, must-revalidate, proxy-revalidate";
            }
        }
    }
}

Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Π³ΠΎΡ‚ΠΎΠ²Ρ‹ΠΉ ΠΎΠ±Ρ€Π°Π· ΠΌΠΎΠΆΠ½ΠΎ здСсь: https://hub.docker.com/r/alexxxnf/nginx-spa. Он Π·Π°Π½ΠΈΠΌΠ°Π΅Ρ‚ 10,5 ΠœΠ‘. ΠžΡ€ΠΈΠ³ΠΈΠ½Π°Π»ΡŒΠ½Ρ‹ΠΉ nginx Π·Π°Π½ΠΈΠΌΠ°Π» 19,7 ΠœΠ‘. Мой спортивный интСрСс ΡƒΠ΄ΠΎΠ²Π»Π΅Ρ‚Π²ΠΎΡ€Ρ‘Π½.

Π£Ρ‡ΠΈΠΌ статику ΠΏΠΎΠ½ΠΈΠΌΠ°Ρ‚ΡŒ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ окруТСния

Для Ρ‡Π΅Π³ΠΎ Π² SPA ΠΌΠΎΠ³ΡƒΡ‚ понадобится настройки? НапримСр, для Ρ‚ΠΎΠ³ΠΎ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΊΠ°ΠΊΠΎΠΉ RESTful API ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ. ΠžΠ±Ρ‹Ρ‡Π½ΠΎ настройки для Π½ΡƒΠΆΠ½ΠΎΠ³ΠΎ окруТСния ΠΏΠ΅Ρ€Π΅Π΄Π°ΡŽΡ‚ΡΡ Π² SPA Π½Π° этапС сборки. Если Π½ΡƒΠΆΠ½ΠΎ Ρ‡Ρ‚ΠΎ-Ρ‚ΠΎ ΠΏΠΎΠΌΠ΅Π½ΡΡ‚ΡŒ, Ρ‚ΠΎ придётся ΠΏΠ΅Ρ€Π΅ΡΠΎΠ±Ρ€Π°Ρ‚ΡŒ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅. Π― этого Π½Π΅ Ρ…ΠΎΡ‡Ρƒ. Π― Ρ…ΠΎΡ‡Ρƒ Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ΡΠΎΠ±ΠΈΡ€Π°Π»ΠΎΡΡŒ ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π· Π½Π° стадии CI, Π° ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€ΠΈΡ€ΠΎΠ²Π°Π»ΠΎΡΡŒ ΡΡ‚ΠΎΠ»ΡŒΠΊΠΎ, сколько Π½ΡƒΠΆΠ½ΠΎ, Π½Π° стадии CD с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… окруТСния.

РазумССтся, статичСскиС Ρ„Π°ΠΉΠ»Ρ‹ сами ΠΏΠΎ сСбС Π½Π΅ ΠΏΠΎΠ½ΠΈΠΌΠ°ΡŽΡ‚ Π½ΠΈΠΊΠ°ΠΊΠΈΡ… ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… окруТСния. ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ, придётся ΠΏΠΎΠΉΡ‚ΠΈ Π½Π° Ρ…ΠΈΡ‚Ρ€ΠΎΡΡ‚ΡŒ. Π’ Ρ„ΠΈΠ½Π°Π»ΡŒΠ½ΠΎΠΌ ΠΎΠ±Ρ€Π°Π·Π΅ я Π±ΡƒΠ΄Ρƒ Π·Π°ΠΏΡƒΡΠΊΠ°Ρ‚ΡŒ Π½Π΅ nginx, Π° ΡΠΏΠ΅Ρ†ΠΈΠ°Π»ΡŒΠ½Ρ‹ΠΉ shell-скрипт, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΏΡ€ΠΎΡ‡ΠΈΡ‚Π°Π΅Ρ‚ ΠΏΡ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ окруТСния, Π·Π°ΠΏΠΈΡˆΠΈΡ‚ ΠΈΡ… Π² статичСскиС Ρ„Π°ΠΉΠ»Ρ‹, соТмёт ΠΈΡ… ΠΈ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ послС этого пСрСдаст ΡƒΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ nginx.

Для этого Π² Dockerfile прСдусмотрСн ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ ENTRYPOINT. ΠŸΠ΅Ρ€Π΅Π΄Π°Π΄ΠΈΠΌ Π΅ΠΌΡƒ Π²ΠΎΡ‚ Ρ‚Π°ΠΊΠΎΠΉ скрипт (Π½Π° ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π΅ Angular):

docker-entrypoint.sh

#!/bin/sh
set -e
FLAG_FILE="/configured"
TARGET_DIR="/etc/nginx/html"
replace_vars () {
  ENV_VARS='$(awk 'BEGIN{for(v in ENVIRON) print "

quot;v}')'
# Π’ Angular ΠΈΡ‰Π΅ΠΌ плСйсхолдСры Π² main-Ρ„Π°ΠΉΠ»Π°Ρ…
for f in "$TARGET_DIR"/main*.js; do
# envsubst замСняСт Π² Ρ„Π°ΠΉΠ»Π°Ρ… плСйсхолдСры Π½Π° значСния ΠΈΠ· ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… окруТСния
echo "$(envsubst "$ENV_VARS" < "$f")" > "$f"
done
}
compress () {
for i in $(find "$TARGET_DIR" | grep -E ".css$|.html$|.js$|.svg$|.txt$|.ttf


quot;); do
# Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ ΠΌΠ°ΠΊΡΠΈΠΌΠ°Π»ΡŒΠ½ΡƒΡŽ ΡΡ‚Π΅ΠΏΠ΅Π½ΡŒ сТатия
gzip -9kf "$i" && brotli -fZ "$i"
done
}
if [ "$1" = 'nginx' ]; then
# Π€Π»Π°Π³ Π½ΡƒΠΆΠ΅Π½, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π²Ρ‹ΠΏΠΎΠ»Π½ΠΈΡ‚ΡŒ скрипт Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΡ€ΠΈ самом ΠΏΠ΅Ρ€Π²ΠΎΠΌ запускС
if [ ! -e "$FLAG_FILE" ]; then
echo "Running init script"
echo "Replacing env vars"
replace_vars
echo "Compressing files"
compress
touch $FLAG_FILE
echo "Done"
fi
fi
exec "$@"

Π§Ρ‚ΠΎΠ±Ρ‹ скрипт сдСлал своё Π΄Π΅Π»ΠΎ, Π² js-Ρ„Π°ΠΉΠ»Π°Ρ… настройки Π½Π°Π΄ΠΎ ΠΏΠΈΡΠ°Ρ‚ΡŒ Π²ΠΎΡ‚ Π² Ρ‚Π°ΠΊΠΎΠΌ Π²ΠΈΠ΄Π΅: ${API_URL}.

Π‘Ρ‚ΠΎΠΈΡ‚ ΠΎΡ‚ΠΌΠ΅Ρ‚ΠΈΡ‚ΡŒ, Ρ‡Ρ‚ΠΎ Π±ΠΎΠ»ΡŒΡˆΠΈΠ½ΡΡ‚Π²ΠΎ соврСмСнных SPA ΠΏΡ€ΠΈ сборкС Π΄ΠΎΠ±Π°Π²Π»ΡΡŽΡ‚ ΠΊ своим Ρ„Π°ΠΉΠ»Π°ΠΌ Ρ…ΡΡˆΠΈ. Π­Ρ‚ΠΎ Π½ΡƒΠΆΠ½ΠΎ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π±Ρ€Π°ΡƒΠ·Π΅Ρ€ ΠΌΠΎΠ³ смСло Π·Π°ΠΊΡΡˆΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Ρ„Π°ΠΉΠ» Π½Π° Π΄Π»ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ срок. Если Ρ„Π°ΠΉΠ» всё-Ρ‚Π°ΠΊΠΈ измСнится, Ρ‚ΠΎ измСнится ΠΈ Π΅Π³ΠΎ Ρ…ΡΡˆ, Ρ‡Ρ‚ΠΎ Π² свою ΠΎΡ‡Π΅Ρ€Π΅Π΄ΡŒ заставит Π±Ρ€Π°ΡƒΠ·Π΅Ρ€ ΡΠΊΠ°Ρ‡Π°Ρ‚ΡŒ Ρ„Π°ΠΉΠ» Π·Π°Π½ΠΎΠ²ΠΎ.

К соТалСнию, Π² ΠΌΠΎΡ‘ΠΌ ΠΌΠ΅Ρ‚ΠΎΠ΄Π΅, ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ Ρ‡Π΅Ρ€Π΅Π· ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ окруТСния Π½Π΅ ΠΏΡ€ΠΈΠ²ΠΎΠ΄ΠΈΡ‚ ΠΊ измСнСнию Ρ…ΡΡˆΠ° Ρ„Π°ΠΉΠ»Π°, Π° Π·Π½Π°Ρ‡ΠΈΡ‚ ΠΈΠ½Π²Π°Π»ΠΈΠ΄ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ кэш Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π° Π½Π°Π΄ΠΎ ΠΊΠ°ΠΊΠΈΠΌ-Ρ‚ΠΎ Π΄Ρ€ΡƒΠ³ΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ. Π£ мСня этой ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ Π½Π΅Ρ‚, ΠΏΠΎΡ‚ΠΎΠΌΡƒ Ρ‡Ρ‚ΠΎ Ρ€Π°Π·Π½Ρ‹Π΅ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ Ρ€Π°Π·Π²ΠΎΡ€Π°Ρ‡ΠΈΠ²Π°ΡŽΡ‚ΡΡ Π² Ρ€Π°Π·Π½Ρ‹Ρ… срСдах.

Π‘ΠΎΠ±ΠΈΡ€Π°Π΅ΠΌ Ρ„ΠΈΠ½Π°Π»ΡŒΠ½Ρ‹ΠΉ ΠΎΠ±Ρ€Π°Π·

НаконСц-Ρ‚ΠΎ.

Dockerfile

# ΠŸΠ΅Ρ€Π²Ρ‹ΠΉ Π±Π°Π·ΠΎΠ²Ρ‹ΠΉ ΠΎΠ±Ρ€Π°Π· для сборки
FROM alexxxnf/spa-builder as builder
# Π§Ρ‚ΠΎΠ±Ρ‹ эффктивнСС ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ кэш Docker-Π°, сначала устанавливаСм Ρ‚ΠΎΠ»ΡŒΠΊΠΎ зависимости
COPY ./package.json ./package-lock.json /app/
RUN cd /app && npm ci --no-audit
# ΠŸΠΎΡ‚ΠΎΠΌ собираСм само ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅
COPY . /app
RUN cd /app && npm run build -- --prod --configuration=docker

# Π’Ρ‚ΠΎΡ€ΠΎΠΉ Π±Π°Π·ΠΎΠ²Ρ‹ΠΉ ΠΎΠ±Ρ€Π°Π· для Ρ€Π°Π·Π΄Π°Ρ‡ΠΈ
FROM alexxxnf/nginx-spa
# Π—Π°Π±ΠΈΡ€Π°Π΅ΠΌ ΠΈΠ· ΠΏΠ΅Ρ€Π²ΠΎΠ³ΠΎ ΠΎΠ±Ρ€Π°Π·Π° сначала компрСссор
COPY --from=builder /usr/local/bin/brotli /usr/local/bin
# ΠŸΠΎΡ‚ΠΎΠΌ добавляСм Ρ‡ΡƒΠ΄ΠΎ-скрипт
COPY ./docker/docker-entrypoint.sh /docker-entrypoint.sh
# И Π² ΠΊΠΎΠ½Ρ†Π΅ Π·Π°Π±ΠΈΡ€Π°Π΅ΠΌ само ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅
COPY --from=builder /app/dist/app /etc/nginx/html/
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]

Π’Π΅ΠΏΠ΅Ρ€ΡŒ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΠ²ΡˆΠΈΠΉΡΡ ΠΎΠ±Ρ€Π°Π· ΠΌΠΎΠΆΠ½ΠΎ ΡΠΎΠ±Ρ€Π°Ρ‚ΡŒ ΠΈ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π³Π΄Π΅-ΡƒΠ³ΠΎΠ΄Π½ΠΎ.

Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ: habr.com