Como publicar un app React en GCP Cloud Run
Junio 2020

Este fin de semana nos dimos la tarea de subir a producción una app en React (la cual veníamos trabajando un par de semanas) y debíamos hacer una demo pública. Decidimos publicarlo en Cloud Run de Google Cloud Platform.
En esta ocasión nos saltaremos como crear una app en react, que ya se explicó en otro post →
Así que revisaremos:
- Como dockerizar una app en React,
- Como subir una imagen de Docker a Goggle Container Registry
- Como configurar el servicio en Cloud Run
Nota: Lo que más me demoró fue el paso 1, tener la imagen de docker funcionando correctamente :s (para los que quieren revisar una guía básica de docker → http://bit.ly/2YnvK9v )
1. Como dockerizar una app en React
Revisaremos:
a) como configurar el entorno de desarrollo con código que se auto recarga y
b) la configuración de una imagen lista para producción.
1.1 Configuración del proyecto
Como requisito debemos tener NodeJS instalado. Nos dirigimos a nuestra carpeta de proyectos ej.: documents/projects/ y generamos nuestra nueva app:
$ npx create-react-app@latest <nombre de nuestra app> --use-npm
Para este ejemplo utilizaremos “demoreact”, como nombre de nuestra app.
$ npx create-react-app@latest demoreact --use-npm
$ cd demoreact
Solo para personalizar e identificar que es nuestra app, en este ejemplo vamos a modificar el archivo scr>App.js
En alguno de los párrafos, que tenemos en ese archivo <p> …….. </p> colocaremos nuestro nombre y el año :)
1.2 Docker
Agregamos el archivo Dockerfile al directorio raíz del proyecto:
# container app "demoreact" - Development v.0.1.0
# by <your name> | <date># pull official base image
FROM node:alpine
# set working directory
WORKDIR "/app"
# add `/app/node_modules/.bin` to $PATH
ENV PATH /app/node_modules/.bin:$PATH
# install app dependencies
COPY ./package.json ./
COPY ./package-lock.json ./
RUN npm install --silent
# add app
COPY . ./
# start app
CMD ["npm", "start"]
Agregamos el archivo .dokerignore:
node_modules
build
.dockerignore
Dockerfile
Dockerfile.prod
Esto ayudará a que la construcción del contenedor sea más rápida, debido a que nuestras dependencias locales dentro del directorio de “node_modules” no serán enviadas al demonio (daemon) de Docker.
Ahora construimos la “imagen” del contenedor Docker:
$ docker build -t demoreact:dev .
— ## AQUI MI PRIMER TROPIEZO ## —
No se llegaba a construir la “imagen” del contenedor :( por varios errores parecidos a:
“ENOENT: no such file or directory, open ‘/app/package.json” y más.
En mi caso en especifico trabaje con una app que ya llevábamos un par de semanas trabajando en local, habíamos instalado varias dependencias para ver si resolvían diferentes necesidades. Estas dependencias se habían quedado instaladas, y algunas de ellas estaban deprecadas y no funcionaban bien. Nos toco hacer limpieza :/
Primero revisamos las dependencias instaladas en el archivo package.json. También podemos listar las mismas con:
npm list
Escogimos 3 que estaban instaladas y que habíamos dejado de usar.
Para desinstalar la dependencia y eliminar del package.json:
npm rm --save <nombre de la dependencia>
si se desea eliminar del desarrollo:
npm rm --save-dev <nombre de la dependencia>
para eliminar el directorio de dependencias, y reinstalar todo (> npm install)
-rd
Ejemplo:
rm -rd /node_modules
npm install
— ## FIN DEL PRIMER TROPIEZO :) ## —
Ahora que tenemos construida la “imagen” del contenedor, procedemos a ejecutarla, para probar de que funcione correctamente antes de subirla a GCP:
docker run \
-it \
--rm \
-v ${PWD}:/app \
-v /app/node_modules \
-p 3001:3000 \
-e CHOKIDAR_USEPOLLING=true \
demoreact:dev
Tambien podemos escribirlo:
$ docker run -it --rm -v ${PWD}:/app -v /app/node_modules -p 3001:3000 -e CHOKIDAR_USEPOLLING=true demoreact:dev
Se interpreta de la siguiente manera:
- Docker run crea y ejecuta un contender de la imagen que acabamos de crear
-it
inicia el contenedor en modo interactivo. Esto es necesario ya que después de la versión de 3.4.1 de “react-scripts”, este se sale después de iniciar el contenedor. Por esto necesitamos el modo interactivo.--rm
elimina el contenedor y volúmenes después de que el contenedor se sale o termina de ejecutarse.-v ${PWD}:/app
monta el código dentro del contenedor en “/app”. (esto no funciona en Windows)-p 3001:3000
expone el puerto 3000 del contenedor al puerto 3001 en nuestro equipo
Ahora podemos abrir nuestro navegador en http://localhost:3001 y deberías poder ver la app. Termina el servidor, con “CTRL + C”, una vez que acabes de hacer tu prueba.
2. Preparando la imagen de la app para producción
Para producción vamos a crear un archivo separado de Dockerfile, lo vamos a llamar Dockerfile.prod
#- container "demoreact" - production v.0.1.0
#- by <your name> | <date># build environment
FROM node:alpine as build# set working directory
WORKDIR /app# install app dependencies
ENV PATH /app/node_modules/.bin:$PATH
COPY package.json ./
COPY package-lock.json ./
RUN npm ci --silent
RUN npm install react-scripts@3.4.1 -g --silent# add app
COPY . ./# build
RUN npm run build
# production environment
FROM nginx:stable-alpine
COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Con este archivo vamos a construir y taggear la imagen docker:
$ docker build -f Dockerfile.prod -t demoreact:prod .
Ejecutamos el contenedor:
$ docker run -it --rm -p 80:80 demoreact:prod
En el navegador colocamos http://localhost y veremos nuestra app :)
2.1 REACT Router y Nginx
Si estamos usando React Router, se debe hacer un cambio en el archivo de config de Nginx.
#- container "demoreact" - production v.0.1.0
#- by <your name> | <date># build environment
FROM node:alpine as build# set working directory
WORKDIR /app# install app dependencies
ENV PATH /app/node_modules/.bin:$PATH
COPY package.json ./
COPY package-lock.json ./
RUN npm ci --silent
RUN npm install react-scripts@3.4.1 -g --silent# add app
COPY . ./# build
RUN npm run build
# production environment
FROM nginx:stable-alpine
COPY --from=build /app/build /usr/share/nginx/html# new
COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Creamos el siguiente folder y dentro colocamos el archivo nginx.conf:
└── nginx
└── nginx.conf
En el archivo nginx.conf colocamos lo siguiente:
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
3. Como publicar en Cloud Run de GCP
Ahora que tenemos nuestra imagen del contenedor lista para producción, vamos a prepararla para subirla a la nube de Google y que se sirva desde Cloud Run.
3.1 Taggeo de imagen para GCP
$ docker tag <IMAGE_ID> gcr.io/<ID_PROJECT>/demoreact:v.0.1
El “IMAGE_ID” lo encontramos con el comando: $ docker images

En este ejemplo el “IMAGE_ID” que corresponde al contenedor “demoreact” y el tag: “prod” es: 194205c1d662
—
Para encontrar el ID_PROJECT, vamos a la consola de Google Cloud, y en la barra azul y hacemos click en el listado de proyectos:

Se desplegará los proyectos que tenemos así como sus IDs.

En este caso el ID_Project es: peak-service-280316 y podemos continuar con el taggeo de la imagen:
$ docker tag 194205c1d662 gcr.io/peak-service-280316/demoreact:v.0.1
Nuevamente ejecutamos $ docker images

3.2 Subimos la imagen a Container Registry
Ya tenemos nuestra imagen taggeada para subirla a Google Container Registry (GCR), que es como un DockerHub pero de Google dentro de su propia infraestructura y que mantiene las imágenes privadas.
$ docker push gcr.io/peak-service-280316/demoreact:v.0.1
En la consola de GCP nos vamos al Container Registry y podemos ver nuestro proyecto “demoreact”

Si hacemos click sobre el proyecto, vemos la imagen docker y su etiqueta:

3.3 Publicamos la imagen
Llego el momento de publicar nuestra app y mostrarla al mundo :)
En la consola de GCP nos vamos a “Cloud Run” que esta en el grupo de “Procesamiento”. Clink en CREAR SERVICIO, aqui para el paso 1 escogemos las siguientes opciones:

- Cloud completamente administrado, y escogemos la región donde queremos que este el servicio, sugiero dejarlo en “us-central1 (Iowa)”
- Nombre del servicio: demoreact
- Autenticación: Permitir invocaciones sin autenticar
Clink en Siguiente, y pasamos a configurar la primera revisión del servicio.
Del listado de proyectos que se muestren, escogemos demoreact, y se lista el ID de la imagen y su tag, en este caso v.0.1.
— ## NOTA ## —
NOTA: Más adelante podemos subir nuevas versiones de la imagen a ese proyecto con tag diferentes. Y podemos cambiar a que imagen apunta el servicio :) eso esta super cool!!
— ## FIN DE LA NOTA ## —
Hacemos click en MOSTRAR CONFIGURACIÓN AVANZADA:
Por defecto el puerto del contenedor que toma Google es el 8080, pero recuerden que en nuestro Dockerfile.prod expusimos el puerto 80 de Nginx.
Y el Ajuste de escala automático, lo configuramos a “Numero máximo de instancias a 3”. No se puede configurar el mínimo.
Esto significa que habra 1 instancia siempre, y si la cantidad máxima de solicitudes (que también se puede modificar) excede las 80, se creará una segunda instancia y lo mismo pasará para una tercera. Para este ejemplo podriamos escalar el servicio hasta 3 instancias.

Finalmente clink en CREAR. Se procesará el nuevo servicio y finalmente nos aparecerá una pantalla como esta:

La URL pública la marcamos con un ovalo rojo en la imagen y …..
TA-TA-TA-TAAAN!!!!
Si hacemos click sobre la URL, nuestra app esta expuesta al mundo entero!!! :)
Nuevamente gracias por leerme!!! Me gustaría escuchar tus comentarios y sugerencias para nutrir este post.
Si te gusto y te es útil compártelo! y dale un 👏 (en los iconos de la izquierda) para que más personas vean este artículo. Comparte el conocimiento en tus redes sociales!