Hai mai lanciato un deploy manuale alle 18:00 del venerdì e scoperto, il lunedì dopo, che un test non era passato? O peggio: hai dimenticato di eseguire la migrazione del database e il sito è andato giù per mezza giornata? Noi, di Meteora Web, abbiamo visto queste scene decine di volte. La soluzione si chiama CI/CD automatizzato, e GitHub Actions è lo strumento più accessibile per iniziare — specialmente se il tuo codice è già su GitHub.
Questa guida parte da zero, ma non è per principianti assoluti: conosci già un po’ di Git e YAML? Bene. Qui capirai davvero come funzionano workflow, job, step e trigger, non solo come copiare un file da una repository template. Imparerai a distinguere cosa va in un job e cosa in uno step, quando usare un trigger su push o su pull request, e perché sbagliare la struttura può costarti tempo e risorse.
Cos’è un workflow? La scatola nera della tua automazione
Un workflow è un processo automatizzato che definisci in un file YAML dentro .github/workflows/. Ogni repository può avere più workflow, ognuno indipendente. Pensa a un workflow come a una “ricetta” che dice: “quando succede X, esegui questa sequenza di operazioni”.
La struttura minima è questa:
name: Deploy su produzione
on:
push:
branches: ["main"]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Esegui deploy
run: echo "Deploy simulato"
Vediamo gli elementi:
- name: nome leggibile (opzionale ma utile).
- on: il trigger — quando far partire il workflow.
- jobs: un insieme di job che girano in parallelo di default.
- runs-on: l’ambiente (es.
ubuntu-latest,windows-latest). - steps: comandi eseguiti uno dopo l’altro dentro il job.
Il file YAML è sensibile all’indentazione. Noi, di Meteora Web, abbiamo visto workflow bloccarsi perché un tab era finito al posto di due spazi. Usa sempre un editor che validi YAML.
Trigger: quando e perché far partire il workflow
Il trigger on è il cuore decisionale. Puoi usare eventi semplici come push, pull_request, schedule (cron), workflow_dispatch (manuale), oppure eventi da altri servizi tramite repository_dispatch.
Un errore comune è usare push su tutti i branch. Per la CI (Continuous Integration) spesso vuoi attivare solo su branch principali e pull request. Esempio:
on:
push:
branches: ["main", "develop"]
pull_request:
branches: ["main"]
Nota che pull_request attiva il workflow quando la PR viene aperta o aggiornata, non quando viene pushato sul branch della PR (a meno che non sia verso main). Questo evita doppie esecuzioni.
Altri trigger utili:
- schedule: per backup notturni o pulizia log.
- workflow_dispatch: per eseguire il workflow a piacere dal tab Actions.
- release: per pubblicare automaticamente su npm o Docker Hub quando si crea una release.
Attenzione ai filtri path: puoi limitare i trigger a file specifici con paths. Esempio: attivare il deploy solo se cambia il file docker-compose.yml. Noi lo usiamo per evitare di far ripartire l’intera pipeline quando modifichi solo il README.
Job: le stanze separate della tua automazione
Un workflow può avere uno o più job. Ogni job gira su un runner indipendente e, di default, in parallelo. Questo è potente: puoi, per esempio, eseguire test su tre versioni di Node.js contemporaneamente, senza aspettare.
Esempio di matrice (matrix strategy):
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node: [16, 18, 20]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm test
Ogni job è isolato: se il job “test su Node 16” fallisce, gli altri continuano. Puoi anche definire dipendenze tra job con needs. Esempio:
jobs:
test:
runs-on: ubuntu-latest
steps: [/* test */]
deploy:
needs: test
runs-on: ubuntu-latest
steps: [/* deploy */]
Qui deploy parte solo se test termina con successo. Attenzione: needs aspetta che tutti i job dichiarati siano completati, non necessariamente con successo. Per eseguire solo su successo, GitHub Actions già lo fa di default: se un job fallisce, i job successivi che lo need non partono.
Job runner e ambienti
Ogni job gira su un runner. I runner ufficiali sono ubuntu-latest, windows-latest, macos-latest (tutti con un set predefinito di tool). Puoi anche usare self-hosted runner se hai esigenze specifiche (es. GPU, database interni). Noi, di Meteora Web, abbiamo configurato runner self-hosted per un cliente che doveva compilare codice su un server on-premise con licenze particolari. La sicurezza è fondamentale: il runner ha accesso al codice, quindi usa solo runner fidati.
Step: i mattoni atomici del lavoro
Dentro ogni job ci sono gli step. Ogni step esegue un’azione atomica: può essere un comando shell (run) oppure un’azione predefinita (uses). Gli step sono sequenziali: se uno fallisce (exit code ≠ 0), gli step successivi non vengono eseguiti, a meno che non usi if: always() o if: failure().
Esempio con azioni della community:
steps:
- name: Checkout codice
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- name: Copia .env
run: cp .env.example .env
- name: Esegui Composer
run: composer install --no-interaction --prefer-dist
- name: Esegui test
run: php artisan test
Ogni step ha un name che appare nei log. Non sottovalutare i nomi: quando un workflow fallisce, un nome chiaro ti fa risparmiare minuti di debug.
Usare le azioni predefinite vs comandi raw
Le actions (nel marketplace) sono pacchetti riutilizzabili. Per operazioni comuni (checkout, setup linguaggio, deployment su cloud) conviene usarle. Per azioni personalizzate (es. script di migrazione), scrivi un comando run direttamente. Noi sconsigliamo di scrivere centinaia di righe di shell inline: meglio spostare la logica in uno script nel repository e chiamarlo con run: bash scripts/deploy.sh.
Errori comuni e come evitarli
1. YAML malformato: un errore di indentazione blocca tutto. Usa yaml-lint o la validazione integrata di GitHub. Noi abbiamo un workflow che lancia yamllint su tutti i file YAML prima di ogni deploy.
2. Trigger troppo ampi: se attivi il workflow su ogni push in ogni branch, ogni commit fa girare la pipeline — incluso il fix di un typo nel README. Filtra con branches e paths-ignore.
3. Variabili d’ambiente in chiaro: mai scrivere password o token direttamente nel file YAML. Usa i secrets di GitHub: ${{ secrets.MY_SECRET }}.
4. Dipendenze errate tra job: se usi needs, ricorda che non puoi scambiare dati tra job se non con artefatti o cache. Per passare un file da un job all’altro, usa actions/upload-artifact e actions/download-artifact.
Esempio completo: CI + Deploy condizionale
Mettiamo insieme tutto in un workflow realistico per un’app Laravel con test e deploy su server VPS via SSH:
name: CI/CD Laravel
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
jobs:
test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_DATABASE: app_test
MYSQL_USER: test
MYSQL_PASSWORD: test
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: pdo, pdo_mysql
- run: cp .env.example .env
- run: composer install -q --no-interaction --prefer-dist
- run: php artisan key:generate
- run: php artisan migrate --force
- run: php artisan test
deploy:
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy via SSH
uses: easingthemes/ssh-deploy@v4
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
remote-host: ${{ secrets.SSH_HOST }}
remote-user: ${{ secrets.SSH_USER }}
source: "."
target: "/var/www/app"
- name: Esegui migrazioni e ottimizzazioni
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/app
php artisan migrate --force
php artisan cache:clear
php artisan config:cache
Nota: il deploy parte solo quando il push è su main e dopo che i test sono passati. Questo è il pattern che noi consigliamo a ogni cliente che inizia con CI/CD.
In sintesi — cosa fare adesso
- Crea il file
.github/workflows/ci.ymlnel tuo repository. Non serve altro per iniziare. - Scegli i trigger giusti: almeno
pushsul branch principale epull_requestsu quello stesso branch. - Definisci almeno un job che esegua i test. Se usi più versioni di linguaggio, approfitta della matrix strategy.
- Separa CI e CD: un job per test, uno per deploy con
needs: teste condizioneifsul branch. - Non esporre segreti: usa sempre
${{ secrets.NOME }}per password, token e chiavi SSH. - Controlla i log dopo il primo run: GitHub mostra ogni step in tempo reale. Se qualcosa fallisce, l’output ti dice perché.
Se hai bisogno di una mano per configurare la pipeline o vuoi che ti aiutiamo a ripulire un workflow esistente, parlaci. Noi, di Meteora Web, lavoriamo su queste cose ogni giorno — dal dominio al fatturato, un unico interlocutore.
Sponsored Protocol