Haciendo el despliegue de una aplicación Angular (Angular-CLI) a producción con Docker y Nginx

Libera tu aplicación Angular a producción usando Docker y Nginx de forma fácil

Aviso: Esta guía podría estar desactualizada. Te recomendarla seguirla solo como punto de entrada.

En la actualidad me encuentro desarrollando mi propia pequeña plataforma de inicio en Angular para mi agencia de desarrollo de tecnología, es por ello que en su momento necesitaré hacer un despliegue de mi sitio que está siendo desarrollado en Angular a producción y quise aprovechar la ocasión para enseñar cómo hacer un despliegue de una aplicación Angular para producción.

Antes de comenzar con la guía, esta hace uso de configuraciones básicas pero con algunas optimizaciones incluídas para Nginx.

Lo primero que hay que tener en cuenta es que estamos seguros que la aplicación está lista, es decir, no hay necesidad de hacer refactorizado de código, borrar componentes innecesarios, que la aplicación funciona correctamente y que no tenemos problema alguno en general, esto, con la finalidad de que no necesiten hacer un cambio, compilar, liberar…y así sucesivamente hasta que logren la estabilidad buscada.

Con esto en mente, es decir, que nuestro código está 100% revisado lo primero que tenemos que tener en cuenta es que debemos tener una aplicación libre de dependencias innecesarias, recordemos que cada dependencia que nosotros agregamos es código que será conjuntado a nuestra aplicación, es por ello que debemos revisar nuestro package.json para corroborar que no tenemos librerías innecesarias.

Imagen obtenida de: http://www.aprende-facilmente.com/angular-2/angular-cli/

Una vez que nos deshicimos de esas librerías obsoletas (si es que las hubo) el siguiente paso es compilar nuestra aplicación, para ello, como el título menciona estamos pensando que esta es una aplicación de Angular-CLI por lo que tenemos acceso a un comando de compilación muy sencillo:

ng build --prod

Con esto, Angular-CLI tomará nuestras dependencias, ejecutará su magia y nos entregará una carpeta llamada dist dentro tendremos todos nuestros archivos disponibles listos para pasar a producción.

Cada vez que nosotros iniciemos de nuevo la aplicación para trabajar en ella con ng serve esta carpeta será borrada, si volvemos a compilar se elimina y se vuelve a crear.

Entendiendo este punto entonces, si utilizas GitHub para subir estos cambios y hacer pull en tu servidor (ya que nosotros haremos un volúmen que jalará los cambios de forma automática para que se desplieguen dentro de Docker, pero de esto veremos más adelante) te recomiendo que esta carpeta la saques con .gitignore y que mantengas una branch master-dev, donde dev sea el branch que puedas trabajar para desarrollo y master únicamente considere esta carpeta. En mi caso, yo hago que dist salga a otra carpeta que tengo y ahí subo los cambios a GitHub.

Ya que hemos generado este archivo dist lo siguiente es tener preparado nuestro Docker, esta guía no te enseñará cómo instalar tu Docker ni a Docker-Compose que son parte de esta guía pero sí te dejo un enlace:

Obtener Docker para Ubuntu

Este enlace es para Ubuntu, tu puedes relacionarlo si gustas en el menú izquierdo a tu sistema operativo de preferencia (Debian, Fedora, CentOS, etcétera)

Imagen obtenida de: https://logz.io/blog/what-is-docker/

Ya que tenemos Docker instalado (junto a Docker-Compose), vamos a generar un Docker-Compose, para que le indique a Docker que cree una imagen para Nginx donde vivirá nuestra aplicación, la vincule y haga su trabajo.

Para ello, creamos un archivo llamado docker-compose.yml en el directorio raíz, por ejemplo /opt/appen este archivo incluiremos la siguiente configuración:

app:
  build: ./nginx
  restart: always
  volumes:
    - ./angular:/var/www/html/angular
  ports:
    - "80:80"
    - "443:443"

Veamos qué hace este archivo, lo que primero hará será crear una imagen llamada app que va a construir Nginx, si hay error interno, se reinicie de forma automática, nos vincule un volúmen de nuestra carpeta angular a la ruta interna y nos abra los puertos 80 y 443 de entrada y salida, el 443 en este caso es por si gustan incluir soporte HTTPS ya que en preferente, esta guía sigue la configuración de Nginx para HTTP2 y es requerido el uso de HTTPS para este protocolo.

Ahora vamos a crear una carpeta llamada angular dentro de esta vamos a soltar todos los archivos que se generó a la hora de compilar en dist . ¡Ya casi lo logramos!.

¿Cuál es el siguiente paso?, bueno, dentro de la raíz, donde vive nuestro archivo docker-compose.yml vamos a crear una carpeta llamada nginx dentro de esta vamos a crear nuestro contenedor de Nginx con las configuraciones necesarias.

Nginx logo obtenido de: https://i1.wp.com/www.ryadel.com/wp-content/uploads/2017/12/nginx-logo.png?fit=735%2C400&ssl=1

Para ello vamos a crear dentro de la carpeta nginx un archivo llamado Dockerfile (sin extensión) y pondremos el siguiente contenido:

FROM ubuntu

MAINTAINER Asfo "[email protected]"

ENV NGINX_VERSION 1.13.4

RUN apt-get update && apt-get install -y ca-certificates build-essential wget libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev

RUN wget http://www.openssl.org/source/openssl-1.0.2d.tar.gz \
  && tar -xvzf openssl-1.0.2d.tar.gz \
  && cd openssl-1.0.2d \
  && ./config \
    --prefix=/usr \
    --openssldir=/usr/ssl \
  && make && make install \
  && ./config shared \
    --prefix=/usr/local \
    --openssldir=/usr/local/ssl \
  && make clean \
  && make && make install

RUN wget http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz \
  && tar -xzvf nginx-${NGINX_VERSION}.tar.gz

COPY conf /nginx-${NGINX_VERSION}/auto/lib/openssl/

RUN cd nginx-${NGINX_VERSION} \
  && ./configure \
    --prefix=/usr/local/nginx \
    --sbin-path=/usr/sbin/nginx \
    --conf-path=/etc/nginx/nginx.conf \
    --pid-path=/var/run/nginx.pid \
    --error-log-path=/var/log/nginx/error.log \
    --http-log-path=/var/log/nginx/access.log \
    --with-http_ssl_module \
    --with-http_v2_module \
    --with-openssl=/usr \
    --with-http_realip_module \
    --with-http_stub_status_module \
    --with-threads \
    --with-ipv6 \
  && make \
  && make install

RUN apt-get autoremove -y

RUN ln -sf /dev/stdout /var/log/nginx/access.log
RUN ln -sf /dev/stderr /var/log/nginx/error.log

VOLUME ["/var/cache/nginx"]

RUN rm -v /etc/nginx/nginx.conf

ADD nginx.conf /etc/nginx/
ADD blockips.conf /etc/nginx/

RUN echo "daemon off;" >> /etc/nginx/nginx.conf

# Certificados en caso de que los tengas para soporte HTTPS + HTTP2
#COPY server.key /etc/nginx/ssl/
#COPY server.crt /etc/nginx/ssl/

EXPOSE 80 443

CMD nginx

En este caso no hay mucho que pueda explicar ya que en realidad es un poco complicado hacerlo, pero, voy a explicar únicamente unos fragmentos por líneas, primero, la línea #1 nos referencia que usaremos Ubuntu como parte del contenedor, la línea #2 es nada más una referencia a quién hizo el Dockerfile, después seteamos una variable de entorno.

Instalamos algunas dependencias internas del contenedor y ….al mismo Nginx con configuraciones específicas (como el módulo http_ssl, el módulo http_v2, openssl, ipv6, entre otras).

Posterior borramos las cosas obsoletas para evitar sobrecarga, hacemos un link de referencia a access.log y error.log y borramos el archivo default de configuración de Nginx que reemplazaremos más adelante.

Re-agregamos el archivo de configuración pero ahora con nuestras configuraciones y por último incluimos un bloqueador de IPs, solo en caso de que te topes con bots o gente que no quieras que acceda en un futuro.

Por último menciono que si tienes certificados deberás incluir en esta misma carpeta (nginx) tu .key y tu .crt. Si no, no te preocupes puedes dejarlo así pero yo recomiendo que uses gratuitos como LetsEncrypt para este paso o compres uno ya que es altamente recomendable el uso de HTTPS. Para finalizar, le pegamos “daemon off;” al archivo de configuración (esto se podría evitar si lo agregamos manual, pero lo dejamos de esta forma porque en mi caso, cuando lo hacía externo me daba un error por alguna razón) exponemos el puerto 80 y 443 y arrancamos Nginx.

Ahora que tenemos nuestro Dockerfile, ¿qué sigue?. Es simple, nuestro archivo de configuración para Nginx y un archivo especial de configuración para OpenSSL. Primero expondremos este archivo de configuración de OpenSSL:

if [ $OPENSSL != NONE ]; then

    case "$CC" in

        cl | bcc32)
            have=NGX_OPENSSL . auto/have
            have=NGX_SSL . auto/have

            CFLAGS="$CFLAGS -DNO_SYS_TYPES_H"

            CORE_INCS="$CORE_INCS $OPENSSL/openssl/include"
            CORE_DEPS="$CORE_DEPS $OPENSSL/openssl/include/openssl/ssl.h"
            CORE_LIBS="$CORE_LIBS $OPENSSL/openssl/lib/ssleay32.lib"
            CORE_LIBS="$CORE_LIBS $OPENSSL/openssl/lib/libeay32.lib"

            # libeay32.lib requires gdi32.lib
            CORE_LIBS="$CORE_LIBS gdi32.lib"
            # OpenSSL 1.0.0 requires crypt32.lib
            CORE_LIBS="$CORE_LIBS crypt32.lib"
        ;;

        *)
            have=NGX_OPENSSL . auto/have
            have=NGX_SSL . auto/have

            CORE_INCS="$CORE_INCS $OPENSSL/include"
            CORE_DEPS="$CORE_DEPS $OPENSSL/include/openssl/ssl.h"
            CORE_LIBS="$CORE_LIBS $OPENSSL/lib/libssl.a"
            CORE_LIBS="$CORE_LIBS $OPENSSL/lib/libcrypto.a"
            CORE_LIBS="$CORE_LIBS $NGX_LIBDL"

            if [ "$NGX_PLATFORM" = win32 ]; then
                CORE_LIBS="$CORE_LIBS -lgdi32 -lcrypt32 -lws2_32"
            fi
        ;;
    esac

else

    if [ "$NGX_PLATFORM" != win32 ]; then

        OPENSSL=NO

        ngx_feature="OpenSSL library"
        ngx_feature_name="NGX_OPENSSL"
        ngx_feature_run=no
        ngx_feature_incs="#include <openssl/ssl.h>"
        ngx_feature_path=
        ngx_feature_libs="-lssl -lcrypto"
        ngx_feature_test="SSL_library_init()"
        . auto/feature

        if [ $ngx_found = no ]; then

            # FreeBSD port

            ngx_feature="OpenSSL library in /usr/local/"
            ngx_feature_path="/usr/local/include"

            if [ $NGX_RPATH = YES ]; then
                ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lssl -lcrypto"
            else
                ngx_feature_libs="-L/usr/local/lib -lssl -lcrypto"
            fi

            . auto/feature
        fi

        if [ $ngx_found = no ]; then

            # NetBSD port

            ngx_feature="OpenSSL library in /usr/pkg/"
            ngx_feature_path="/usr/pkg/include"

            if [ $NGX_RPATH = YES ]; then
                ngx_feature_libs="-R/usr/pkg/lib -L/usr/pkg/lib -lssl -lcrypto"
            else
                ngx_feature_libs="-L/usr/pkg/lib -lssl -lcrypto"
            fi

            . auto/feature
        fi

        if [ $ngx_found = no ]; then

            # MacPorts

            ngx_feature="OpenSSL library in /opt/local/"
            ngx_feature_path="/opt/local/include"

            if [ $NGX_RPATH = YES ]; then
                ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lssl -lcrypto"
            else
                ngx_feature_libs="-L/opt/local/lib -lssl -lcrypto"
            fi

            . auto/feature
        fi

        if [ $ngx_found = yes ]; then
            have=NGX_SSL . auto/have
            CORE_LIBS="$CORE_LIBS $ngx_feature_libs $NGX_LIBDL"
            OPENSSL=YES
        fi
    fi

    if [ $OPENSSL != YES ]; then

cat << END

$0: error: SSL modules require the OpenSSL library.
You can either do not enable the modules, or install the OpenSSL library
into the system, or build the OpenSSL library statically from the source
with nginx by using --with-openssl=<path> option.

END
        exit 1
    fi

fi

Este no lo explicaré, pero, hay que crear un archivo llamado conf (sin extensión) dentro de la carpeta nginx y listo, es todo lo que haremos con este conf .

Ahora sí, vamos a la configuración de Nginx:

worker_processes 4;

events
{
  worker_connections 1024;
}

http
{

  include blockips.conf;
  
  # Si tienes SSL descomenta estas líneas para la redirección forzada a HTTPS
  #server
  #{
  #  listen 80;
  #  return 301 https://$host$request_uri;
  #}

  server
  {

    server_tokens off;
    # Si tienes SSL descomenta esta líneas
    # ssl on;
    server_name tudominio.com;

    root /var/www/html/angular;
    index index.html;
    
    # Si tienes SSL descomenta estas líneas
    # listen 443 ssl http2;

    include mime.types;
    default_type application/octet-stream;

    sendfile on;

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

    gzip on;
    gzip_disable "msie6";
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    # Si tienes SSL descomenta estas líneas
    # ssl_certificate /etc/nginx/ssl/server.crt;
    # ssl_certificate_key /etc/nginx/ssl/server.key;

    location ~ \.css
    {
      add_header Content-Type text/css;
    }
    location ~ \.js
    {
      add_header Content-Type application/javascript;
    }

    location /
    {
      expires -1;
      add_header Pragma "no-cache";
      add_header Cache-Control "no-store, no-cache, must-revalidate, post-check=0, pre-check=0";
      try_files $uri $uri/ /index.html;
    }

    location ~* \.(?:manifest|appcache|html?|xml|json)$
    {
      expires -1;
    }

    location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$
    {
      expires 1M;
      access_log off;
      add_header Cache-Control "public";
    }

    location ~* \.(?:css|js)$
    {
      expires 1d;
      access_log off;
      add_header Cache-Control "public";
    }
  }
}

En esta configuración marcamos que queremos 4, pero aquí se recomienda que ustedes marquen la cantidad de workers dependiendo a cuántos núcleos tiene el servidor donde está corriendo Nginx.

Luego le indicamos que queremos que sirva hasta 1024 clientes a la vez, por ahora si no tienen un tráfico elevado, es una buena configuración o incluso pueden bajarla. Posterior tenemos nuestro bloque de HTTP, en este, creamos nuestro servidor de Nginx para que nos maneje a tudominio.com que obviamente deberemos cambiar por el dominio que tendrá la aplicación (o sub-dominio), esto se hace en la línea que menciona server_name, el server_tokens indica que Nginx no enviará ninguna token de servidor para evitar que el cliente pueda espiar nuestra versión de Nginx.

Posterior marcamos que nuestra raíz está en /var/www/html/angular esto es por si liberarán múltiples aplicaciones en esta configuración puedan hacerlo de forma simple y además, aquí vinculamos nuestro contenedor de Docker.

Indicamos nuestro archivo index como index.html, incluimos los tipos de mimes (si no saben qué es un mime, no lo explicaré en esta guía) y marcamos el default como octet, activamos el sendfile para optimización (no explicaré qué hace esta configuración), indicamos dónde estarán nuestros registros de error y acceso, habilitamos la compresión gzip para optimización en la carga del sitio y los tipos de mimes comprimibles.

Por último asignamos los headers específicos para cada tipo de archivo (esto resuelve un bug que en ocasiones sucede, no sé por qué lo hace a veces y a veces no, pero mejor incluir para evitar fallos) en CSS y JS.

Por penúltimo en el caso de nuestra localización raíz, es decir, cuando acceden a la URL principal como https://tudominio.com marcamos los cachés y marcamos el try_files, en palabras simples, el try_files trata de empatar la URL solicitada a la raíz de tu archivo, es decir trata de relacionar /login por poner un ejemplo a la ruta de Angular. Sin esta configuración si un cliente accede directo a esa URL le saldrá un error 404 en Nginx.

Cabe destacar que, en caso de 404 con esta configuración, Angular solo mostrará el 404 que le hayas indicado (si es que le indicaste alguno). Por último indicamos los cachés para cada tipo de archivo y ¡Finalizado!.

¿Qué falta?. Solo un paso más, no desesperes. El paso restante es crear un archivo llamado: blockips.conf en la carpeta nginx con el siguiente contenido:

deny 5.228.100.253;
deny 176.77.9.144;
deny 94.180.134.52;
deny 95.24.146.125;

En este caso les incluí IPs que detecté que se dedican a recargar la página una y otra vez y son IPs rusas (o así se me indicó en el rastreo de estas), por lo que no te preocupes está muy bien que estén bloqueadas.

Ya solamente hay que estar en la raíz de nuestro proyecto y corremos el comando:

docker-compose up -d --build --remove-orphans

Que compila nuestro contenedor, lo levanta y si había algo viejo, lo quita y corre en el fondo.

Docker hará todo el trabajo y nuestro dominio (si ya están las DNS propiamente redirigidas al servidor) mostrará nuestra aplicación de Angular
¡Listo!.

¿Qué sigue?, si reconstruyes tu aplicación, recuerda solamente debes obtener el dist e incluirlo en la carpeta angular borrando los contenidos previos para evitar que se sature de archivos ahí o usar Git como menciono y hacer un push/pull y Docker se encargará de jalar los cambios de forma automática al contenedor y Nginx de hacer su trabajo (recuerda, hay caché, así que si no ves los cambios, borra la caché del sitio, normalmente CTRL+R en Windows / Chrome por ejemplo).

No ocupas regenerar nada ni hacer nada más que ese push/pull con Git y listo, todo está funcionando.

¿Tienes dudas?, déjalas en los comentarios, pronto pondré un repositorio para esta guía.

¿Cuál es tu reacción?
+1
0
+1
0
+1
0
+1
0
+1
2
Total
0
Shares
Publicación anterior

Desarrollando un filtro de búsqueda para Angular

Siguiente Publicación

Haciendo gráficas en “tiempo real” con Angular, ChartJS y  NodeJS

Publicaciones Relacionadas