1,409 24/03/2026 07/04/2026 8 min

Un deploy può sembrare riuscito e restare comunque fragile. Il caso tipico è una CI minima che carica l’artifact, riavvia il servizio e non verifica altro.

Qui lavoriamo su uno scenario concreto: build in GitLab CI, artifact versionato, rilascio su una VPS con systemd e rollback rapido. L’obiettivo è una mini checklist post-deploy. Pochi comandi, ma scelti bene.

Il punto non è “fare DevOps”. Il punto è capire, in due minuti, se il deploy ha introdotto drift, se l’artifact è quello giusto e se il rollback è davvero disponibile.

Prerequisiti

  • Un runner GitLab CI o una pipeline equivalente.
  • Un artifact firmato o almeno identificato con hash e commit SHA.
  • Accesso SSH alla VPS.
  • Un servizio gestito da systemd, con unit file dedicata.
  • Un endpoint HTTP o una health route per il controllo finale.

Note: i comandi sotto assumono una release installata in /opt/app/releases/<versione> e un symlink stabile /opt/app/current.

Warning: se il deploy modifica anche configurazione o env, la verifica dell’artifact non basta. Serve un controllo di drift sulla configurazione attiva.

Step 1 — Blocca il riferimento dell’artifact prima di toccare il servizio

Prima di riavviare, devi sapere esattamente quale build stai installando. Il commit SHA e l’hash del pacchetto evitano ambiguità durante il rollback.

In GitLab CI, salva almeno nome file, SHA del commit e checksum. Se l’artifact è un tarball, meglio ancora se lo firmi o ne pubblichi il checksum nel job.

# nel job di build
sha256sum dist/app.tar.gz | tee dist/app.tar.gz.sha256
printf '%s\n' "$CI_COMMIT_SHA" > dist/commit.txt

# Output:

dist/app.tar.gz.sha256 e dist/commit.txt presenti tra gli artifact della pipeline.

Su server, verifica subito il pacchetto prima dell’estrazione.

cd /opt/app/incoming
sha256sum -c app.tar.gz.sha256
cat commit.txt

# Output:

app.tar.gz: OK e commit mostrato a video.

Perché conta: se il file è corrotto o sostituito, il problema non si scoprirà dopo il restart. Lo scopri qui, con il servizio ancora stabile.

Checklist rapida

  • Checksum corrispondente.
  • Commit coerente con la pipeline approvata.
  • Nome release univoco, mai latest.

Step 2 — Verifica che il filesystem contenga solo la release attesa

Molti rollback falliscono perché la release vecchia è stata cancellata troppo presto. Mantieni almeno due release e un symlink stabile.

Controlla la struttura prima del switch. Questo riduce gli errori dovuti a rilasci parziali.

ls -la /opt/app/releases
readlink -f /opt/app/current
find /opt/app/releases -maxdepth 1 -type d -printf '%f\n' | sort

# Output:

due o più directory di release e /opt/app/current che punta alla release attiva.

Se la release nuova esiste ma manca un file atteso, non andare avanti. Il problema spesso nasce da artifact incompleto o exclude errato nella pipeline.

Note: un deploy con artifact parziali è peggiore di un deploy fermo. Almeno il servizio precedente resta recuperabile.

Step 3 — Confronta unit systemd, env e config con la release precedente

Qui cerchi drift. Il servizio può partire, ma con variabili diverse, limiti diversi o file di configurazione vecchi.

Prima controlla l’unit attiva e i drop-in. Poi confronta i file di configurazione rispetto alla release precedente.

systemctl cat app.service
systemctl show app.service -p FragmentPath -p DropInPaths -p Environment
cmp -s /opt/app/releases/previous/.env /opt/app/releases/current/.env; echo $?

# Output:

unit e drop-in leggibili, variabili attese presenti, codice di cmp pari a 0 se i file coincidono.

Se il file env deve cambiare tra release, il confronto non deve essere identico. In quel caso verifica solo le chiavi consentite.

grep -E '^(APP_ENV|APP_VERSION|API_BASE_URL)=' /opt/app/releases/current/.env

# Output:

sole variabili previste e valori coerenti con il deploy.

Warning: il drift più subdolo è quello “silenzioso”. Il servizio è online, ma legge una configurazione diversa da quella del repository.

Step 4 — Riavvia il servizio e controlla subito log e stato

Il restart non è la verifica. È solo il punto in cui la verifica diventa possibile.

Riavvia solo dopo i controlli precedenti. Subito dopo, leggi stato e ultimi log. Non aspettare il primo alert.

systemctl restart app.service
systemctl is-active app.service
systemctl status app.service --no-pager -l
journalctl -u app.service -n 30 --no-pager

# Output:

active come stato e nessun errore grave nei log.

Se il servizio entra in degraded, il problema può essere nel listener, nelle dipendenze o nella migrazione non completata. Fermati qui.

Verifica anche che il processo usi la release corretta.

pid=$(systemctl show -p MainPID --value app.service)
readlink -f /proc/$pid/exe 2>/dev/null || true
tr '\0' '\n' < /proc/$pid/environ | grep -E '^APP_VERSION='

# Output:

binary o launcher atteso e variabile versione coerente con la release appena deployata.

Step 5 — Esegui la health check applicativa, non solo il ping

Un TCP open non prova che l’app funzioni. Serve una route che tocchi almeno il path critico: DB, cache o backend esterno, se il flusso lo richiede.

Usa curl con timeout brevi. Se la risposta è lenta, il deploy è solo apparentemente riuscito.

curl -fsS --max-time 3 https://app.example.com/health
curl -fsS --max-time 3 https://app.example.com/ready

# Output:

HTTP 200 e payload atteso, ad esempio {"status":"ok"} o ready.

Se la route readiness fallisce ma la health risponde, hai trovato un problema di dipendenze o warm-up. Non forzare il traffico.

Per applicazioni con endpoint più ricco, controlla anche un campo versione.

curl -fsS https://app.example.com/health | jq -r '.version,.commit'

# Output:

versione e commit uguali a quelli della pipeline.

Step 6 — Verifica che l’artifact servito coincida con quello pubblicato

Questo è il controllo che chiude il cerchio. La release installata deve coincidere con quella annunciata dalla CI.

Se il deploy è file-based, confronta il contenuto del manifest o del file build-info. Se è un binario, usa una versione incorporata.

cat /opt/app/current/build-info.json
sha256sum /opt/app/current/app.tar.gz 2>/dev/null || true

# Output:

commit, timestamp e versione allineati al job di pipeline.

Se usi una pagina di diagnostica interna, è ancora meglio. Ti evita SSH su ambienti con accesso limitato.

Note: l’obiettivo non è collezionare dati. L’obiettivo è rispondere a una domanda semplice: “sto eseguendo proprio questo artifact?”

Step 7 — Confronta la configurazione attiva con quella desiderata per scoprire drift

Il drift non riguarda solo gli env. Può colpire unit file, sysctl, timer, limiti ulimit e file in /etc.

Una verifica minima utile confronta checksum o diff tra ciò che la pipeline ha distribuito e ciò che il server ha attivo.

diff -u /opt/app/releases/previous/config/app.yaml /opt/app/releases/current/config/app.yaml || true
systemd-delta --type=extended

# Output:

nessuna differenza inattesa nei file applicativi e nessun delta sorpresa sull’unit.

Se trovi delta non previsti, il server è già uscito dalla configurazione standard. Qui nasce il rischio di deploy non ripetibili.

Per infrastrutture più rigide, controlla anche i pacchetti installati.

dpkg -l | grep -E 'app-|libfoo|libbar'
# oppure
rpm -qa | grep -E 'app-|libfoo|libbar'

# Output:

solo versioni previste dal baseline dell’ambiente.

Verifica finale

La checklist finale deve stare in meno di tre minuti. Se richiede più tempo, qualcosa è troppo fragile o troppo manuale.

  • Checksum dell’artifact valido.
  • Release e commit allineati alla pipeline.
  • systemd active senza errori recenti.
  • Health e readiness rispondono con 200.
  • Config attiva uguale o compatibile con quella desiderata.
  • Rollback disponibile con la release precedente intatta.

Se vuoi un controllo ancora più concreto, salva l’esito in un file di audit.

printf '%s %s %s\n' "$(date -Is)" "$(readlink -f /opt/app/current)" "$(systemctl is-active app.service)" \
  >> /var/log/app-postdeploy.log

# Output:

una riga audit leggibile, utile per incident review e change log.

Troubleshooting

1) app.service: Failed with result 'exit-code'

Causa: la nuova release parte con env o file mancanti, spesso dopo un artifact incompleto.

Fix: controlla i log e torna alla release precedente.

journalctl -u app.service -n 50 --no-pager
ln -sfn /opt/app/releases/previous /opt/app/current
systemctl restart app.service

# Output:

servizio riportato online sulla release precedente.

2) curl: (28) Connection timed out after 3001 milliseconds

Causa: il processo è avviato, ma la readiness resta bloccata su DB, cache o bootstrap lento.

Fix: verifica le dipendenze e aumenta solo se il ritardo è atteso.

systemctl status app.service --no-pager -l
ss -ltnp | grep ':443\|:80'
curl -fsS --max-time 10 https://app.example.com/ready

# Output:

diagnosi chiara sul collo di bottiglia e readiness eventualmente recuperata.

3) sha256sum: WARNING: 1 computed checksum did NOT match

Causa: artifact corrotto, troncato o sostituito durante il trasferimento.

Fix: elimina il pacchetto e rilancialo dalla pipeline o dal registro artifact.

rm -f /opt/app/incoming/app.tar.gz
# rilancia download o deploy job
sha256sum -c app.tar.gz.sha256

# Output:

checksum valido solo dopo un nuovo trasferimento corretto.

Conclusione

Un deploy affidabile non finisce con il restart. Finisce quando artifact, servizio, endpoint e configurazione raccontano la stessa storia.

La mini checklist qui sopra serve proprio a questo: ridurre il tempo tra “sembra tutto ok” e “sappiamo davvero che è ok”. Il prossimo passo concreto è automatizzare questi controlli in un job post-deploy separato, con exit code non ambiguo e rollback già pronto.

Se la tua pipeline non ha ancora un audit di rilascio, aggiungilo adesso. Bastano pochi comandi, ma devono essere sempre gli stessi.