ADR260416

2026/04/16 · Marco Orlandin · 6 min

GitFlow-lite con release-please per deploy automatici su progetto cliente

Autore: Marco Orlandin, Architect
Data: 16 Aprile 2026
Status: Implementato
Settore: Enterprise / CI-CD pipeline
Constraint principali: Team singolo (consulente esterno), progetto Django su AWS ECS Fargate, cliente non tecnico, deploy staging e produzione separati, nessun GitHub Team plan (no approval gate nativo)

Nota: Progetto reale, anonimizzato per riservatezza (NDA). Metriche e dettagli semplificati, ma decisioni e lezioni apprese fedeli alla realtà.

Contesto e problema

Applicazione web Django per un cliente, ad uso operativo quotidiano. Stack Django + Celery + PostgreSQL, deploy su AWS ECS Fargate via GitHub Actions. Due ambienti target: staging (deploy da branch develop) e produzione (deploy da tag).

Il problema: come strutturare il flusso git → CI → deploy in modo che:

  1. Staging si aggiorni automaticamente ad ogni push su develop
  2. Produzione si aggiorni solo su release esplicita
  3. La release sia tracciabile (cosa è cambiato, quando, perché)
  4. Il processo non dipenda dalla memoria di una persona

Il contesto rende la scelta meno ovvia di quanto sembri. È un progetto cliente, non un prodotto proprio. Il cliente non leggerà mai un changelog. Il team è una persona. L'overhead di qualsiasi processo ricade interamente su chi scrive codice.

Requisiti non funzionali

  • Deploy staging automatico su push a develop
  • Deploy produzione automatico su tag semantico
  • Tracciabilità: ogni release deve avere un changelog leggibile
  • Zero intervento manuale per il deploy (nessun "ok adesso mando in prod")
  • Basso overhead quotidiano per uno sviluppatore singolo
  • Compatibile con GitHub Free plan (no branch protection rules avanzate)

Opzioni valutate

Opzione 1: Push manuale + tag manuale

  • Pro: Zero setup. Nessun tool aggiuntivo. Massima flessibilità.
  • Contro: Il deploy dipende da una decisione umana ("ora taggo"). Il changelog non esiste o è scritto a mano (quindi non esiste). La history diventa un muro di "fix", "update", "wip" dopo due settimane. Già sperimentato su altri progetti: è il motivo per cui sto valutando alternative.

Opzione 2: GitFlow completo (Gitflow-avh)

  • Pro: Branching model maturo e documentato. Supporto per hotfix, release branch, feature branch. Ampiamente conosciuto.
  • Contro: Troppi branch per un team di una persona. Release branch che vivono giorni senza senso quando non c'è un QA separato. Merge da release/* verso main E develop è cerimonia che non aggiunge valore. Overhead sproporzionato per il contesto.

Opzione 3: Trunk-based development + tag manuale

  • Pro: Semplicità massima. Un branch, feature flag dove serve. Adottato da Google, raccomandato da DORA.
  • Contro: Richiede feature flag infrastruttura che non esiste ancora. Ogni push va in staging E potenzialmente in produzione, e serve disciplina o gating che GitHub Free non offre. Per un consulente esterno su codice cliente, il rischio di pushare qualcosa di rotto in un branch che triggera il deploy è troppo alto.

Opzione 4: GitFlow-lite + commitizen + release-please

  • Pro: Due branch (develop, main). develop è il branch di default, ogni push deploya staging. release-please apre una PR automatica con version bump + changelog quando ci sono commit convenzionali accumulati. Merge della PR tagga e deploya produzione. Il changelog si scrive da solo. Il tag è il trigger, non una decisione.
  • Contro: Richiede conventional commits (disciplina). Commitizen è un tool in più nella toolchain. release-please è una GitHub Action da configurare e mantenere. Il developer deve capire il formato dei commit message.

Decisione

Scelta l'Opzione 4: GitFlow-lite con commitizen per conventional commits e release-please per release automatiche.

Motivazioni principali:

  • Il costo dell'Opzione 1 (push manuale) non si paga al setup, si paga ogni volta che serve capire cosa è cambiato. Dopo un mese di "fix stuff" nella history, il debug di un deploy rotto diventa archeologia.
  • GitFlow completo è progettato per team con ruoli separati (dev, QA, release manager). Un team di una persona non ha bisogno di release branch che vivono tre giorni.
  • Trunk-based richiede infrastruttura (feature flag, gating) che non esiste e non vale la pena costruire per questo progetto.
  • L'Opzione 4 ha il rapporto costo/beneficio migliore: il costo è un hook pre-commit e un workflow GitHub Actions. Il beneficio è deploy tracciabili, changelog automatici, e nessuna decisione umana nel path verso produzione.

Implementazione passo-passo

1. Branch develop come default

GitHub default branch impostato su develop, non main. Tutti i push quotidiani vanno su develop. main riceve merge solo da release-please. Questo protegge la produzione senza branch protection rules (che richiedono GitHub Team plan).

2. Conventional commits via commitizen

.commitlintrc.yaml nel repo. Hook pre-commit via commitizen che rifiuta commit message non conformi. Formato: type(scope): description dove type è feat, fix, chore, docs, refactor, test, ci. Lo scope è opzionale ma incoraggiato.

3. release-please per version bump e changelog

GitHub Action release-please configurata su push a main. Accumula commit convenzionali, apre una PR con:

  • Version bump in VERSION (semantic versioning)
  • CHANGELOG.md generato automaticamente
  • Commit di release

Al merge della PR, release-please crea il tag git. Il tag triggera il workflow di deploy produzione.

4. CI/CD pipeline a due stage

feature/* → develop  →  [CI: test + build + deploy staging]
                              ↓
               release-please PR (auto)
                              ↓
develop → main (merge PR) → tag → [CI: build + deploy production]

Il deploy staging include: Docker build, ECR push, DB migration (ECS task one-off), ECS service update, ALB smoke test. Il deploy produzione è identico, con variabili d'ambiente diverse.

5. Nessun approval gate automatico

GitHub Free plan non supporta required reviewers su branch protection. L'approval gate è umano: la PR di release-please resta aperta finché non viene mergiata manualmente. È l'unico punto di decisione umana nel flusso, e serve come "sei sicuro?" prima di andare in produzione.

Conseguenze osservate

Positive

  • Deploy tracciabili: ogni release ha un tag semantico e un changelog. "Cosa c'è in produzione?" ha sempre una risposta.
  • Zero decisioni nel deploy path: staging è automatico. Produzione è un merge di PR. Nessun "adesso taggo manualmente".
  • History leggibile: conventional commits forzano una struttura. git log --oneline racconta una storia, non una lista della spesa.
  • Onboarding semplificato: se il progetto passa a un altro sviluppatore, il flusso è documentato nel codice (workflow, hook, config).

Negative

  • Frizione quotidiana: ogni commit deve rispettare il formato. Per fix veloci, la tentazione di fare git commit --no-verify è reale. Resistere è disciplina.
  • Dipendenza da GitHub Actions: release-please è una Action. Se GitHub Actions ha downtime, il flusso si blocca. Mitigazione: il deploy manuale resta possibile come fallback.
  • Overhead percepito: per un singolo sviluppatore, conventional commits possono sembrare cerimonia. Il valore emerge nel tempo, non al primo commit.

Quando non applicare questo approccio

  • Prototipi e spike: se il codice potrebbe essere buttato domani, conventional commits sono zavorra.
  • Team con release manager dedicato: GitFlow completo o trunk-based con feature flag sono probabilmente più adatti.
  • Progetti senza CI/CD: se il deploy è manuale, release-please non ha un trigger su cui agire. Va prima messo in piedi un deploy automatizzato, poi ha senso pensare alla release automation.
  • Monorepo con servizi multipli: release-please gestisce un progetto per repo. Per monorepo servono configurazioni aggiuntive (manifest mode) o tool diversi.

Stack utilizzato

  • VCS: Git, GitHub (Free plan)
  • Branching: GitFlow-lite (develop + main)
  • Commit convention: Conventional Commits via commitizen
  • Release automation: release-please (GitHub Action)
  • CI/CD: GitHub Actions (test, build, deploy)
  • Deploy target: AWS ECS Fargate (staging + produzione)
  • Container registry: AWS ECR