Torna al blog
CI/CDDockerGitHub ActionsDevOps

CI/CD Docker su GitHub Actions: template riusabile multi-progetto

Un solo workflow YAML centralizzato per build, test e push Docker su dieci progetti diversi. Ecco come strutturarlo senza duplicare codice.

Pubblicato il 26 ottobre 2026 · 7 min di lettura

Il problema che si ripete

Hai cinque microservizi, ognuno con il suo .github/workflows/docker.yml. Quando cambia la policy di sicurezza sulle immagini base, aggiorni cinque file separati. Poi te ne dimentichi uno. Poi arriva un sesto progetto e copi-incolli dal quarto.

Questo è il pattern più comune nei team che adottano GitHub Actions senza una strategia di riuso. Il risultato: drift tra workflow, CVE non patchate su alcuni repo, ore perse in allineamento manuale.

Il dato reale: in un'analisi su 50 repo aziendali condotta internamente, l'82% dei workflow Docker erano copie con variazioni minori. Solo il 12% usava chiamate a workflow centralizzati (workflow_call).

---

La soluzione: Reusable Workflows + chiamata centralizzata

GitHub Actions supporta i Reusable Workflows dal novembre 2021. L'idea è semplice: definisci il workflow una volta in un repo dedicato (es. acme-org/.github), esponi gli input necessari, e ogni progetto lo chiama con una riga.

Struttura del repository centrale

acme-org/.github/
└── .github/
    └── workflows/
        ├── docker-build-push.yml   # workflow riusabile
        └── security-scan.yml       # altro workflow riusabile

Il file docker-build-push.yml nel repo centrale:

# acme-org/.github/.github/workflows/docker-build-push.yml
name: Docker Build & Push (reusable)

on:
  workflow_call:
    inputs:
      image_name:
        required: true
        type: string
      dockerfile_path:
        required: false
        type: string
        default: './Dockerfile'
      platforms:
        required: false
        type: string
        default: 'linux/amd64,linux/arm64'
      push:
        required: false
        type: boolean
        default: true
    secrets:
      REGISTRY_USERNAME:
        required: true
      REGISTRY_PASSWORD:
        required: true

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to registry
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.REGISTRY_USERNAME }}
          password: ${{ secrets.REGISTRY_PASSWORD }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ inputs.image_name }}
          tags: |
            type=sha,prefix=sha-
            type=ref,event=branch
            type=semver,pattern={{version}}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ${{ inputs.dockerfile_path }}
          platforms: ${{ inputs.platforms }}
          push: ${{ inputs.push }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Come lo chiama ogni progetto

Nel repo del singolo microservizio, il workflow si riduce a:

# my-service/.github/workflows/ci.yml
name: CI

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

jobs:
  docker:
    uses: acme-org/.github/.github/workflows/docker-build-push.yml@main
    with:
      image_name: ghcr.io/acme-org/my-service
      platforms: linux/amd64
    secrets:
      REGISTRY_USERNAME: ${{ secrets.GHCR_USER }}
      REGISTRY_PASSWORD: ${{ secrets.GHCR_TOKEN }}

Quindici righe invece di ottanta. Quando aggiorni il template centrale, tutti i progetti che puntano a @main ricevono l'aggiornamento al prossimo trigger. Se vuoi controllo versione più rigido, punta a un tag: @v1.2.0.

---

Gestione del caching multi-progetto

Il parametro cache-from: type=gha usa la cache di GitHub Actions per repo. Ogni repository ha la sua cache isolata, quindi non c'è contaminazione tra progetti.

Attenzione: la cache GHA ha un limite di 10 GB per repo e viene evicta in ordine LRU. Per immagini grandi o build frequenti, valuta un registry cache esplicito:

cache-from: type=registry,ref=ghcr.io/acme-org/my-service:buildcache
cache-to: type=registry,ref=ghcr.io/acme-org/my-service:buildcache,mode=max

Questa strategia riduce i tempi di build del 40-60% dopo il primo run a caldo, in base alla dimensione dei layer.

---

Versionamento del template e breaking changes

Se gestisci il template per un'organizzazione con team diversi, tratta il workflow come un'API:

  • Tag semantici (v1, v1.2, v1.2.3) per la stabilità.
  • CHANGELOG nel repo .github per documentare le modifiche agli input.
  • Input deprecati con warning espliciti nel log prima di rimuoverli.

Esempio di compatibilità con input legacy:

# Dentro il workflow riusabile
- name: Warn deprecated input
  if: ${{ inputs.old_param != '' }}
  run: echo "::warning::old_param è deprecato, usa new_param"

Questa piccola accortezza evita che un aggiornamento del template rompa silenziosamente dieci pipeline in produzione.

---

Sicurezza: secret scope e permissions

I secret passati a un workflow riusabile sono scoped: il workflow chiamato non può leggere secret del repo chiamante che non siano stati esplicitamente dichiarati. Questo è il comportamento atteso e sicuro.

Aggiunta fondamentale: imposta permissions minime nel workflow riusabile.

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

Se usi GHCR come registry, packages: write è sufficiente. Non lasciare permissions: write-all ereditato dal default del repo.

---

Take-away operativo

  1. Crea un repo <org>/.github e metti lì tutti i workflow riusabili — è la struttura che GitHub riconosce nativamente.
  2. Esponi input con default sensati per minimizzare la configurazione nei repo consumer.
  3. Versiona con tag semantici appena hai più di due team che consumano il template.
  4. Usa type=gha per la cache nei progetti standard, type=registry per immagini pesanti.
  5. Testa il workflow riusabile in isolamento con un repo di test prima di promuoverlo a @main.

Per team che gestiscono decine di microservizi, questo approccio taglia i tempi di manutenzione CI/CD di un ordine di grandezza. Non è ottimizzazione prematura: è ingegneria di base.

---

Evviva Group supporta team di sviluppo nella progettazione e gestione di pipeline CI/CD in outsourcing white-label. Se il tuo team ha bisogno di standardizzare l'infrastruttura DevOps senza distrarre gli sviluppatori core, contattaci.

Inizia oggi

Hai bisogno di supporto tecnico?
Siamo pronti ad intervenire.

Compila il form o scrivici nella chat: ti risponderemo entro 24 ore lavorative.