Dockerizzare una applicazione Angular

D

In questo tutorial mostreremo come dockerizzare un applicazione Angular creata e buildata con la Angular CLI, usando Docker sia per lo sviluppo che per eseguire gli unit test e per il deploy finale in produzione.

premessa

Prima di iniziare con il vero focus di questo articolo volevo fare una premessa sui vantaggi di utilizzare Docker anche in fase di sviluppo; quando non conoscevo bene Docker pensavo che quest’ultimo fosse utile solo in fase di deploy in produzione, per avere una build riproducibile o per fornire un container per il ciclo di CI. Mi sbagliavo di molto: Docker e’ utilissimo anche in fase di sviluppo!

ambiente di sviluppo pronto per l uso

Sulla macchina di Marco funziona e sulla mia no!

Quante volte avete sentito colleghi imprecare sbattendo la testa sul pc per configurare l’ambiente di sviluppo ? Configurare un ambiente di sviluppo Angular e’ abbastanza semplice, ma in altri contesti ci può’ volere diverse ore per çonfigurare il necessario per tirare su in locale l ambiente di sviluppo. Se dockerizzate la vostra applicazione questo problema non sussiste, un nuovo collega potrà’ essere produttivo in 5 minuti e iniziare a lavorare senza perdere tempo.

verificare il funzionamento in ambiente simile a quello di produzione

Il giorno del go live in produzione ad un problema riscontrato:

Questa funzionalità’ l avevo testata sul mio computer e funzionava!

Una situazione molto ricorrente  e’  la differenza di OS o versione di servizi tra l’ambiente di produzione e l’ambiente locale dove si sviluppa l’applicazione. Sviluppando su un container docker questo problema viene pressoché’ annullato: il container che gira sulla tua macchina avrà’ le stesse caratteristiche di quello che girerà’ in produzione.

ambiente di sviluppo isolato

Prima di utilizzare Docker la mia macchina con cui sviluppavo aveva una miriade di servizi installati: se dovevo sviluppare e fare supporto per diverse applicazioni che facevano uso di  versioni differenti di servizi, lib e framework implicava di installare quest’ultimi nella mia macchina. Il risultato era una macchina di sviluppo caotica e pesante. Da quando uso Docker installo un servizio solo se e’ strettamente necessario; quando devo sviluppare su un progetto mi basterà’ tirare su i relativi container che avranno al loro interno lib e servizi necessari .

dockerizzare un applicazione angular

Finita questa premessa iniziamo con il nostro esercizio di questo articolo: dockerizzare un applicazione Angular.

PROJECT SETUP

  • Docker v19.3.1
  • Angular CLI v8.1.0 ( per creare il progetto)
  • Node xxx ( giusto per poter installare la cli)

Andiamo a creare una nuova applicazione Angular facendo uso della CLI

ng new demo

container di sviluppo

Andiamo quindi a creare un immagine per poter tirare su il container di sviluppo. Il nostro container dovrà’  avere tutti i servizi necessari a tirare su un web service locale con hot reloading facendo uso della cli di angular ( in particolare il comando ng serve)

  1. Creiamo innanzitutto il file .dockerignore nella root del progetto.
node_modules
.git
.gitignore
dist

In questo modo siamo sicuri di non copiare nel container i file non necessari.

2) Creiamo quindi il dockerfile di sviluppo:

Dockerfile.dev

FROM node:12.9.0-alpine

WORKDIR '/app'

COPY package.json .
RUN npm install

COPY . .
EXPOSE 4200

CMD ["npm", "start"]

Come si puo’ vedere stiamo andando a creare un immagine minimale con lo stretto necessario al nostro container di sviluppo:

  • immagine base: node:12.9.0-alpine
  • esplicitiamo che il container espone la porta 4200 la quale andra’ mappata in fase di run con una porta della propria macchina per poter visualizzare nel browser della nostra macchina l’anteprima della web app.
  • Il comando che viene eseguito dal container e’ quello specificato dallo script start del package json
  • Come si puo notare l’immagine e’ veramente minimale e non e’ stato installato globalmente la cli di Angular. Per avviare e buildare l’applicazione utilizzeremo npx che ci permetterà di utilizzare la cli utilizzando le dipendenze Angular locali del progetto e senza quindi installare globalmente Angular.

3) npm start: andiamo a modificare nel package.json del nostro progetto l’istruzione eseguita dallo script start:

"scripts": {
"ng": "ng",
"start": "npx ng serve --host 0.0.0.0 --poll=500",
"build": "npx ng build --prod demo",
"test": "npx ng test --browsers=ChromeHeadless",
"lint": "npx ng lint",
"e2e": "npx ng e2e"
},

L’opzione host e poll sono necessarie per poter visualizzare la web app nel nostro browser con la feature di hot reloading. Maggiori info:

Volume file changes are not detected in container on Windows 10 host: it seems that on Windows FS events don't work properly with host mounts, so you should rather use polling to enable live-reload:

For those who are getting into the same troubles: To achieve this with angular-cli, "poll": 1000 has to be inserted into the defaults-object in angular-cli.json. Unfortunately, this is not well documented. see angular/angular-cli#1814

4) Per poter tirare su il container più’ agevolmente faremo uso di docker-compose evitando di dover specificare da shell ogni volte le opzioni necessarie. Creiamo quindi il file docker-compose.yaml

version: '3'
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports: 
      - "4200:4200"
    volumes:
      - /app/node_modules
      - .:/app

Da notare:

  • il dockerfile da utilizzare ( quello creato al punto precedente)
  • il mapping di porta per mappare la porta 4200 della nostra macchina con quella 4200 del container
  • Abbiamo definito due volumi
    • node_modules come bookmark il quale non e’ bindato all’esterno del container. In questo modo i file delle dipendenze installate esisteranno solo all’interno del container.
    • .:/app: Questo comando monta la directory corrente host ( quella del progetto angular locale) nel contenitore  /app  . In questo modo se modifichiamo uno dei file del nostro progetto la modifica viene percepita all’interno del container il quale grazie al hot reload di ng serve refreshera’ l anteprima dell applicazione

A questo punto possiamo lanciare il container definito con la cli di docker-compose

docker-compose up --build

Potremmo accedere all’anteprima della web app mentre sviluppiamo accedendo all’ url http://localhost:4200/ come facevamo di consueto senza container.

container per unit test

Per poter girare gli unit di test di Angular e’ necessario avere un browser. Headless Chrome  e’ un tool molto utile per far girare automated test in environment dove non e’ pratico lanciare un browser. In rete ho trovato una immagine docker ad hoc per eseguire unit test la quale offre un container minimale con node js + headless chrome: https://github.com/avatsaev/angular-chrome-headless-docker 

Non ho usato direttamente quella immagine perché non aveva un tag version con node 12. Pertanto ho copiato parte del dockerfile di tale repository andando a modifare la versione di node ( in futuro potrei fare una pull request al repo originale).  Ecco il dockerfile di test: Dockerfile.test

FROM debian:stable-slim

RUN apt-get update
RUN apt-get install -yy wget curl gnupg
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - && \
  apt-get update && apt-get install -y nodejs && \
  npm i -g npm@6

RUN node -v && npm -v

RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
  echo "deb http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list && \
  apt-get update && \
  apt-get install -y google-chrome-stable xvfb
RUN npm -v
RUN apt update && apt install -y procps
RUN apt clean
RUN rm -rf /var/lib/apt/lists/*


WORKDIR '/app'


COPY package.json .
RUN npm install

COPY . .

CMD ["npm", "test"]

Per poter usare la versione headless di chrome ho dovuto modificare lo script test e il file configurazione di karma.

"test": "npx ng test --browsers=ChromeHeadless",

karma.conf.js

// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html

module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage-istanbul-reporter'),
      require('@angular-devkit/build-angular/plugins/karma')
    ],
    client: {
      clearContext: false // leave Jasmine Spec Runner output visible in browser
    },
    coverageIstanbulReporter: {
      dir: require('path').join(__dirname, './coverage/demo'),
      reports: ['html', 'lcovonly', 'text-summary'],
      fixWebpackSourcePaths: true
    },
    reporters: ['progress', 'kjhtml'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['ChromeHeadless', 'Chrome'],
    customLaunchers: {
      ChromeHeadless: {
        base: 'Chrome',
        flags: [
          '--headless',
          '--disable-gpu',
          '--no-sandbox',
          '--remote-debugging-port=9222'
        ]
      }
    },	
    singleRun: false,
    restartOnFileChange: true
  });
};

A questo punto ho editato il file docker-compose.yaml per aggiungere il descrittore per il container di test. Ecco la versione definitiva del file docker-compose.yaml:

version: '3'
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports: 
      - "4200:4200"
    volumes:
      - /app/node_modules
      - .:/app
      
  tests:
    build:
      context: .
      dockerfile: Dockerfile.test
    volumes:
      - /app/node_modules
      - .:/app

A questo punto e’ possibile lanciare in un solo colpo i due container ( sviluppo e test) con il comando della cli di docker-compose:

docker-compose up --build

container per deploy

Durante lo sviluppo, in genere si utilizza il comando ng serve per creare, controllare e servire l’applicazione dalla memoria locale, utilizzando webpack-dev-server. Quando sei pronto per il deploy, tuttavia, devi utilizzare il comando ng build per creare l’app e distribuire gli artefatti di compilazione tramite un webserver come nginx  o haproxy. In questo paragrafo mostreremo come servire la build di angular da nginx. Creiamo quindi il file Dockerfile ( nb e’ una bestpractices chiamare Dockerfile l’immagine di produzione mentre quello di sviluppo con il suffisso .dev)

FROM node:12.9.0-alpine as builder
WORKDIR '/app'
COPY package*.json ./
RUN npm ci
COPY . .
RUN npx ng build --prod demo

FROM nginx
EXPOSE 80
COPY --from=builder /app/dist/demo /usr/share/nginx/html

Come si puo’ notare abbiamo creato una pipeline dove in primis andiamo a buildare la webapp e copiamo il risultato della build nell’apposita folder di nginx. A questo possiamo buildare l’immagine e eseguire il run andando a mappare una porta della nostra macchina con la porta 80 del container che espone nginx.

docker build -t italiancoders/angular-demo-app .

docker run -p 8081:80 italiancoders/angular-demo-app

 

A proposito di me

Dario Frongillo

Uno degli admin di Italiancoders e dell iniziativa devtalks.
Dario Frongillo è un software engineer e architect, specializzato in Web API, Middleware e Backend in ambito cloud native. Attualmente lavora presso NTT DATA, realtà di consulenza internazionale.
E' membro e contributor in diverse community italiane per developers; Nel 2017 fonda italiancoders.it, una community di blogger italiani che divulga articoli, video e contenuti per developers.

Gli articoli più letti

Articoli recenti

Commenti recenti