Salta al contenuto principale

1 - GitHub Actions: CI/CD per App Angular da Zero

Guida completa per configurare GitHub Actions per un progetto Angular: build, test, lint, deploy automatico e ottimizzazione workflow.

Cosa ne pensi di questo articolo?

1 - GitHub Actions: CI/CD per App Angular da Zero
DevOps FrontendFoto:Bibek ghoshPexels

In Breve

Guida completa per configurare GitHub Actions per un progetto Angular: build, test, lint, deploy automatico e ottimizzazione workflow.

Introduzione: CI/CD per Sviluppatori Frontend

Ogni sviluppatore frontend prima o poi si trova ad affrontare la stessa domanda: come posso automatizzare il processo di build, test e deploy della mia applicazione? La risposta moderna si chiama CI/CD (Continuous Integration / Continuous Deployment), e GitHub Actions rappresenta oggi lo strumento più accessibile e potente per implementarlo direttamente nel repository del progetto.

In questo primo articolo della serie DevOps per Sviluppatori Frontend, costruiremo da zero una pipeline completa per un'applicazione Angular: dal primo workflow di integrazione continua fino al deploy automatico in produzione. Ogni concetto viene spiegato con esempi pratici e codice pronto all'uso.

Cosa Imparerai in Questo Articolo

  • I concetti fondamentali di GitHub Actions: workflow, job, step e trigger
  • Come configurare un workflow CI per Angular (install, lint, test, build)
  • Come implementare il deploy automatico su Firebase e Vercel
  • Strategie di caching per velocizzare le pipeline
  • Matrix strategy per testare su più versioni di Node.js
  • Come configurare artifacts e branch protection rules
  • Come creare workflow riutilizzabili

1. Concetti Fondamentali di GitHub Actions

GitHub Actions e una piattaforma di automazione integrata in GitHub. Ogni automazione si basa su quattro concetti chiave che formano una gerarchia precisa:

Gerarchia di GitHub Actions

Concetto Descrizione Analogia
Workflow Un file YAML in .github/workflows/ che definisce l'intera automazione La ricetta completa
Job Un gruppo di step che viene eseguito su un runner specifico Una fase della ricetta
Step Un singolo comando o azione all'interno di un job Un'istruzione specifica
Trigger L'evento che avvia il workflow (push, PR, schedule, ecc.) Il momento in cui iniziare

Un workflow può contenere più job, e ogni job può contenere più step. I job vengono eseguiti in parallelo per default, ma possono essere configurati con dipendenze per l'esecuzione sequenziale.

Struttura di un Workflow YAML

Ogni workflow risiede nella directory .github/workflows/ del repository. Ecco la struttura base di un file workflow:


# .github/workflows/ci.yml
name: CI Pipeline

# Trigger: quando eseguire il workflow
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

# Variabili di ambiente globali
env:
  NODE_VERSION: '20'

# I job da eseguire
jobs:
  build:
    # Runner: macchina virtuale su cui eseguire
    runs-on: ubuntu-latest

    # Step del job
    steps:
      # Step 1: Checkout del codice
      - name: Checkout repository
        uses: actions/checkout@v4

      # Step 2: Setup Node.js
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}

      # Step 3: Installazione dipendenze
      - name: Install dependencies
        run: npm ci

      # Step 4: Build
      - name: Build application
        run: npm run build
      

I Trigger Principali

I trigger determinano quando un workflow viene avviato. GitHub Actions supporta decine di eventi, ma per i progetti frontend i più utilizzati sono:


on:
  # Su ogni push ai branch specificati
  push:
    branches: [main, develop]
    paths:
      - 'src/**'
      - 'package.json'

  # Su ogni Pull Request verso main
  pull_request:
    branches: [main]
    types: [opened, synchronize, reopened]

  # Esecuzione programmata (cron)
  schedule:
    - cron: '0 6 * * 1'  # Ogni lunedi alle 6:00 UTC

  # Trigger manuale
  workflow_dispatch:
    inputs:
      environment:
        description: 'Target environment'
        required: true
        default: 'staging'
        type: choice
        options:
          - staging
          - production
      

Attenzione ai Trigger su Push

Il filtro paths e molto utile per evitare build inutili: se modifichi solo il README, non ha senso rieseguire l'intera pipeline. Tuttavia, fai attenzione a non escludere file critici come angular.json o tsconfig.json che influenzano la build.

2. Workflow CI Completo per Angular

Costruiamo ora un workflow di Continuous Integration completo per un progetto Angular. Questo workflow eseguira in sequenza: installazione dipendenze, linting, test unitari e build di produzione.


# .github/workflows/ci.yml
name: Angular CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  NODE_VERSION: '20'

jobs:
  lint:
    name: Lint Check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      - run: npm ci
      - run: npx ng lint

  test:
    name: Unit Tests
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      - run: npm ci
      - run: npx ng test --watch=false --browsers=ChromeHeadless --code-coverage
      - name: Upload coverage report
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/
          retention-days: 7

  build:
    name: Production Build
    runs-on: ubuntu-latest
    needs: test
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      - run: npm ci
      - run: npx ng build --configuration=production
      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/
          retention-days: 3
      

Questo workflow definisce tre job in sequenza grazie alla keyword needs: prima il lint, poi i test, infine la build. Se il lint fallisce, i test non vengono eseguiti; se i test falliscono, la build non parte.

Caching Avanzato delle Dipendenze

La keyword cache: 'npm' nell'action setup-node fornisce un caching base. Per un controllo più granulare, possiamo usare direttamente l'action actions/cache:


  - name: Cache node_modules
    id: cache-npm
    uses: actions/cache@v4
    with:
      path: |
        node_modules
        ~/.npm
      key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
      restore-keys: |
        ${{ runner.os }}-npm-

  - name: Install dependencies
    if: steps.cache-npm.outputs.cache-hit != 'true'
    run: npm ci
      

La chiave di cache usa l'hash del file package-lock.json: quando le dipendenze non cambiano, la cache viene riutilizzata e l'installazione viene completamente saltata. Questo può ridurre il tempo di pipeline di 30-60 secondi per ogni esecuzione.

3. Matrix Strategy: Test Multi-Versione

La matrix strategy permette di eseguire lo stesso job con combinazioni diverse di parametri. Per un progetto Angular, e particolarmente utile per testare su più versioni di Node.js:


  test-matrix:
    name: Test on Node ${{ matrix.node-version }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18, 20, 22]
      fail-fast: false
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      - run: npm ci
      - run: npx ng test --watch=false --browsers=ChromeHeadless
      

L'opzione fail-fast: false assicura che tutti i job della matrice vengano completati anche se uno fallisce, permettendo di identificare tutte le incompatibilita in un solo run.

Quando Usare la Matrix Strategy

  • Test su più versioni di Node.js (LTS corrente e precedente)
  • Test su più sistemi operativi (ubuntu, windows, macos)
  • Build per ambienti diversi (staging, production)
  • Test con diverse versioni di browser

4. CD: Deploy Automatico su Firebase

Dopo aver costruito e testato l'applicazione, il passo successivo e il deploy automatico. Vediamo come configurare il deploy su Firebase Hosting, una delle scelte più comuni per applicazioni Angular.


# .github/workflows/deploy.yml
name: Deploy to Firebase

on:
  push:
    branches: [main]

jobs:
  build-and-deploy:
    name: Build and Deploy to Production
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build for production
        run: npm run build

      - name: Deploy to Firebase Hosting
        uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: ${{ secrets.GITHUB_TOKEN }}
          firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
          channelId: live
          projectId: my-angular-project
      

Preview Deploy per Pull Request

Una funzionalità potente di Firebase Hosting e la possibilità di creare preview channel automatici per ogni Pull Request, permettendo al team di verificare le modifiche prima del merge:


# .github/workflows/preview.yml
name: Deploy Preview

on:
  pull_request:
    branches: [main]

jobs:
  preview:
    name: Deploy Preview Channel
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run build

      - name: Deploy to Preview Channel
        uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: ${{ secrets.GITHUB_TOKEN }}
          firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
          projectId: my-angular-project
          # Senza channelId: crea automaticamente un preview channel
      

Deploy su Vercel

Per chi preferisce Vercel come piattaforma di hosting, la configurazione e altrettanto semplice:


# .github/workflows/deploy-vercel.yml
name: Deploy to Vercel

on:
  push:
    branches: [main]

env:
  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Vercel CLI
        run: npm install -g vercel

      - name: Pull Vercel Configuration
        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}

      - name: Build with Vercel
        run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}

      - name: Deploy to Vercel
        run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
      

5. Artifacts: Condividere File tra Job

Gli artifacts permettono di condividere file tra job diversi dello stesso workflow. Questo e fondamentale per separare la build dal deploy senza ricompilare:


jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npx ng build --configuration=production

      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: angular-dist
          path: dist/my-app/browser/
          retention-days: 1

  deploy:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Download build artifact
        uses: actions/download-artifact@v4
        with:
          name: angular-dist
          path: dist/

      - name: Deploy
        run: |
          # Il contenuto della build e ora in dist/
          ls -la dist/
      

Best Practice per gli Artifacts

  • Imposta sempre retention-days per non sprecare spazio di archiviazione
  • Usa nomi descrittivi per gli artifact (angular-dist, coverage-report)
  • Carica solo i file necessari, non l'intera directory node_modules
  • Per file molto grandi, considera la compressione prima dell'upload

6. Branch Protection Rules

Le branch protection rules di GitHub permettono di imporre vincoli sui branch critici. Quando combinate con i workflow CI, creano una barriera di qualità automatica.

Per configurare le branch protection rules, vai su Settings > Branches > Add branch protection rule nel tuo repository GitHub. Le impostazioni consigliate per il branch main sono:

  • Require a pull request before merging: Impedisce push diretti a main
  • Require status checks to pass before merging: I workflow CI devono passare
  • Require branches to be up to date before merging: Il branch deve essere aggiornato
  • Require conversation resolution before merging: Tutti i commenti devono essere risolti

Per selezionare quali status check richiedere, devi prima eseguire il workflow almeno una volta. Dopo, i nomi dei job appariranno nella lista di selezione:


Status checks richiesti:
  [x] Lint Check          (dal workflow CI)
  [x] Unit Tests          (dal workflow CI)
  [x] Production Build    (dal workflow CI)
      

7. Workflow Riutilizzabili

Quando si gestiscono più repository con configurazioni simili, i reusable workflows evitano la duplicazione del codice. Un workflow riutilizzabile viene definito con il trigger workflow_call:


# .github/workflows/reusable-angular-ci.yml
name: Reusable Angular CI

on:
  workflow_call:
    inputs:
      node-version:
        description: 'Node.js version'
        required: false
        default: '20'
        type: string
      build-command:
        description: 'Build command to execute'
        required: false
        default: 'npm run build'
        type: string
    secrets:
      FIREBASE_SA:
        description: 'Firebase Service Account'
        required: false

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
          cache: 'npm'
      - run: npm ci
      - run: npx ng lint
      - run: npx ng test --watch=false --browsers=ChromeHeadless
      - run: ${{ inputs.build-command }}
      

Per utilizzare questo workflow riutilizzabile da un altro workflow:


# .github/workflows/ci.yml (nel repository che lo consuma)
name: CI

on:
  push:
    branches: [main]

jobs:
  angular-ci:
    uses: my-org/shared-workflows/.github/workflows/reusable-angular-ci.yml@main
    with:
      node-version: '20'
      build-command: 'npm run build -- --configuration=production'
    secrets:
      FIREBASE_SA: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
      

Vantaggi dei Workflow Riutilizzabili

  • DRY: Un solo punto di manutenzione per la logica CI/CD
  • Standardizzazione: Tutti i repository seguono le stesse pratiche
  • Versionamento: Puoi riferire una specifica versione del workflow (@main, @v1)
  • Sicurezza: I segreti vengono passati esplicitamente, non condivisi globalmente

8. Esempio Completo: Pipeline End-to-End

Combiniamo tutti i concetti in una pipeline completa che gestisce CI e CD con branch protection:


# .github/workflows/pipeline.yml
name: Full Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  NODE_VERSION: '20'

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      - run: npm ci
      - run: npx ng lint

  test:
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      - run: npm ci
      - run: npx ng test --watch=false --browsers=ChromeHeadless --code-coverage
      - uses: actions/upload-artifact@v4
        with:
          name: coverage
          path: coverage/

  build:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      - run: npm ci
      - run: npx ng build --configuration=production
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

  deploy-preview:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: ${{ secrets.GITHUB_TOKEN }}
          firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
          projectId: my-angular-project

  deploy-production:
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    needs: build
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: ${{ secrets.GITHUB_TOKEN }}
          firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
          channelId: live
          projectId: my-angular-project
      

L'opzione concurrency in cima al workflow e cruciale: se fai più push ravvicinati sullo stesso branch, il workflow precedente viene cancellato automaticamente, risparmiando minuti di esecuzione.

9. Debugging e Best Practice

Quando un workflow fallisce, il debugging può essere frustrante. Ecco le strategie più efficaci:

Abilitare il Debug Logging

Puoi abilitare i log di debug aggiungendo dei segreti al repository:

  • ACTIONS_RUNNER_DEBUG = true per log dettagliati del runner
  • ACTIONS_STEP_DEBUG = true per log dettagliati di ogni step

Step Condizionali per il Debug


  - name: Debug info
    if: failure()
    run: |
      echo "Node version: $(node -v)"
      echo "NPM version: $(npm -v)"
      echo "Directory: $(pwd)"
      ls -la
      cat package.json
      

Best Practice per GitHub Actions

  • Usa npm ci invece di npm install per installazioni deterministiche
  • Fissa sempre le versioni delle action (@v4 invece di @main)
  • Configura concurrency per evitare build duplicate
  • Usa la cache per ridurre i tempi di esecuzione
  • Separa i job per avere feedback rapido (lint fallisce subito, non dopo la build)
  • Usa environment per proteggere i segreti di produzione
  • Imposta timeout-minutes per evitare job bloccati
  • Monitora il consumo dei minuti GitHub Actions nel piano del tuo account

Conclusione

In questo articolo abbiamo costruito una pipeline CI/CD completa per un'applicazione Angular usando GitHub Actions. Abbiamo esplorato i concetti fondamentali (workflow, job, step, trigger), implementato il caching delle dipendenze, configurato la matrix strategy per test multi-versione, e impostato il deploy automatico su Firebase e Vercel.

La chiave del successo con le pipeline CI/CD e l'iterazione progressiva: inizia con un workflow semplice (lint + build), poi aggiungi i test, infine il deploy automatico. Non cercare di implementare tutto in una volta.

Nel prossimo articolo della serie, esploreremo Docker per sviluppatori frontend, imparando a containerizzare un'applicazione Angular con Dockerfile multi-stage e a gestire ambienti di sviluppo con Docker Compose.

Domande frequenti

Qual è il significato di CI/CD e come può essere implementato da un sviluppatore frontend?

Il processo di build, test e deploy della mia applicazione può essere automatizzato utilizzando la tecnologia CI/CD (Continuous Integration / Continuous Deployment), che rappresenta oggi lo strumento più accessibile e potente per implementarlo direttamente nel repository del progetto.

Quali sono i concetti chiave di GitHub Actions?

I quattro concetti chiave di GitHub Actions sono il workflow, il job, lo step e il trigger. Un workflow può contenere più job, e ogni job può contenere più step. I job vengono eseguiti in parallelo per default, ma possono essere configurati con dipendenze per l'esecuzione sequenziale.

Come posso utilizzare la matrix strategy per testare su più versioni di Node.js?

La matrix strategy permette di eseguire lo stesso job con combinazioni diverse di parametri. Per un progetto Angular, è particolarmente utile per testare su più versioni di Node.js utilizzando l'opzione assicurando che tutti i job della matrice vengano completati anche se uno fallisce.

Come posso configurare il deploy automatico su Firebase?

Per configurare il deploy automatico su Firebase, è necessario creare un workflow di Continuous Deployment (CD) che esegua il deploy automatico dell'applicazione dopo averla costruita e testata.

Fonti e approfondimenti

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti