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-daysper 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=trueper log dettagliati del runnerACTIONS_STEP_DEBUG=trueper 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 ciinvece dinpm installper installazioni deterministiche - Fissa sempre le versioni delle action (
@v4invece di@main) - Configura
concurrencyper 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
environmentper proteggere i segreti di produzione - Imposta
timeout-minutesper 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.

Commenti
Caricamento commenti...
Accedi per commentare