In grafica, il problema non è quasi mai il codice. È la ripetibilità del rilascio.
Un sito con anteprime SVG, export PNG e librerie di rendering può passare in staging e rompersi in produzione dopo un riavvio. Il sintomo è classico: file generati diversi, asset mancanti, cache incoerente, dipendenze cambiate senza volerlo.
Qui ha senso confrontare due approcci. Il primo è pipeline minima con artifact immutabili. Il secondo è deploy diretto con rollback rapido. Entrambi funzionano. Risolvono problemi diversi.
Note: l’esempio sotto usa una web app grafica con export lato server, build statica e servizio systemd. Non è un tutorial generico. Il punto è fermare il drift tra macchina di build e macchina di produzione.
Prerequisiti
- GitLab CI attivo su un progetto con runner Linux.
- Un VPS o server con systemd e una directory di deploy, per esempio /srv/grafica.
- Uno script di build che produca un artefatto deterministico, per esempio dist.tar.zst.
- Accesso SSH con chiave e utente dedicato al deploy.
- Un servizio web o worker che legga i file rilasciati da una directory stabile.
Warning: se la build dipende da font installati a mano sul server, sei già dentro l’environment drift. La pipeline lo scoprirà troppo tardi.
Step 1: definire il flusso minimo di CI
Prima si decide cosa produrre. Poi si decide come distribuirlo. L’artefatto deve contenere tutto ciò che serve al runtime, o quasi tutto.
Per una web app grafica, un flusso minimale è questo:
- installazione dipendenze;
- build degli asset;
- archiviazione dell’output come artifact;
- deploy dell’artifact sul server;
- switch atomico del symlink;
- rollback sul release precedente.
Una pipeline essenziale in GitLab può essere così:
stages:
- build
- deploy
variables:
RELEASES_DIR: /srv/grafica/releases
CURRENT_DIR: /srv/grafica/current
ARTIFACT_NAME: dist.tar.zst
build:
stage: build
image: node:20-bookworm
script:
- npm ci
- npm run build
- tar -I 'zstd -19' -cf $ARTIFACT_NAME dist
artifacts:
name: "$CI_COMMIT_SHORT_SHA"
paths:
- dist.tar.zst
expire_in: 7 days
only:
- main
deploy:
stage: deploy
image: alpine:3.20
needs:
- job: build
artifacts: true
before_script:
- apk add --no-cache openssh-client rsync zstd
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
script:
- scp dist.tar.zst $DEPLOY_USER@$DEPLOY_HOST:/tmp/$CI_COMMIT_SHORT_SHA.tar.zst
- ssh $DEPLOY_USER@$DEPLOY_HOST 'bash -s' << 'EOF'
set -euo pipefail
RELEASES_DIR=/srv/grafica/releases
CURRENT_DIR=/srv/grafica/current
RELEASE_ID='"$CI_COMMIT_SHORT_SHA"'
mkdir -p "$RELEASES_DIR/$RELEASE_ID"
tar -I zstd -xf "/tmp/$RELEASE_ID.tar.zst" -C "$RELEASES_DIR/$RELEASE_ID"
ln -sfn "$RELEASES_DIR/$RELEASE_ID/dist" "$CURRENT_DIR"
EOF
only:
- main
Questa versione è volutamente semplice. Però già elimina il problema principale: il server non ricostruisce nulla. Riceve un pacchetto già pronto.
Step 2: scegliere artifact o deploy diretto
Qui la differenza pratica è importante.
Approccio A: artifact immutabili
La build avviene in CI, in un ambiente controllato. Il risultato viene impacchettato e distribuito. In produzione non si installa nulla: si estraggono file e si punta un symlink alla release nuova.
Vantaggi:
- ripetibilità alta;
- stesso output tra staging e produzione;
- rollback semplice: basta ripuntare il symlink;
- meno dipendenze sul server;
- debug più facile, perché ogni release è identificabile.
Limiti:
- artifact più pesante;
- serve disciplina sul packaging;
- se il runtime richiede binari nativi o librerie di sistema, vanno inclusi o fissati con attenzione.
Approccio B: deploy diretto con rollback
Qui il server riceve i sorgenti o un checkout del repository e lancia la build in loco. È più rapido da impostare. È anche più fragile.
Vantaggi:
- meno passaggi iniziali;
- più semplice se il server è già l’ambiente di build;
- utile per applicazioni piccole o tool interni.
Limiti:
- drift quasi inevitabile nel tempo;
- build non riproducibile se cambiano pacchetti, versioni o font;
- rollback meno affidabile se il server ha già modificato lo stato;
- più rischio di differenze tra macchine.
Regola pratica: se un rilascio fallito costa più di qualche minuto di setup, conviene l’artifact immutabile. Se invece il servizio è piccolo, poco critico e il rollback serve solo come rete di sicurezza, il deploy diretto può bastare.
Step 3: costruire un artifact davvero utile
Un artifact non deve essere solo un archivio di file. Deve essere una unità di rilascio. In pratica, deve contenere:
- asset compilati;
- manifest o hash di verifica;
- eventuali file statici di runtime;
- versione dell’app;
- script di avvio o configurazione minima.
Per esempio, se la tua app genera rendering server-side, puoi impacchettare:
dist/con frontend statico;server/con bundle Node o binario;assets/con template e icone;release.jsoncon commit, data e checksum.
Un esempio di file di versione:
{
"commit": "a1b2c3d4",
"pipeline_id": 1842,
"built_at": "2026-03-25T10:15:00Z",
"node": "20.11.1"
}
Questo aiuta quando devi capire quale release è in esecuzione. Se un export PNG fallisce, non cerchi nel buio. Sai esattamente cosa è stato distribuito.
Step 4: deploy atomico sul server
Il deploy diretto via copia nella directory attiva è una cattiva idea. È la ricetta perfetta per file parziali e stati intermedi. Meglio usare release versionate e un symlink stabile.
Struttura consigliata:
/srv/grafica/
releases/
a1b2c3d4/
e5f6g7h8/
current -> /srv/grafica/releases/a1b2c3d4/dist
shared/
logs/
uploads/
Il flusso è questo:
- carichi il pacchetto in una directory temporanea;
- estrai in una nuova release;
- verifichi i file;
- agganci il symlink
currentalla release nuova; - riavvii il servizio solo se necessario.
Con systemd, il restart può essere minimale:
[Unit]
Description=Grafica App
After=network.target
[Service]
Type=simple
User=grafica
WorkingDirectory=/srv/grafica/current
ExecStart=/usr/bin/node server/index.js
Restart=always
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
Se il servizio legge il codice o i file dalla directory current, il cambio di release è quasi istantaneo. Se qualcosa va male, torni alla release precedente con un solo comando.
Step 5: rollback rapido e affidabile
Il rollback non deve essere un evento eccezionale. Deve essere un’operazione prevista.
La forma più semplice è tenere le ultime N release e un file che indichi la release attiva. In caso di problema, si punta il symlink alla release precedente e si riavvia il servizio.
Esempio di script di rollback:
#!/usr/bin/env bash
set -euo pipefail
RELEASES_DIR=/srv/grafica/releases
CURRENT_LINK=/srv/grafica/current
PREVIOUS_RELEASE=$(ls -1t "$RELEASES_DIR" | sed -n '2p')
if [ -z "$PREVIOUS_RELEASE" ]; then
echo "Nessuna release precedente disponibile"
exit 1
fi
ln -sfn "$RELEASES_DIR/$PREVIOUS_RELEASE/dist" "$CURRENT_LINK"
systemctl restart grafica
Questo approccio è efficace solo se le release sono davvero immutabili. Se la directory viene toccata dopo il deploy, il rollback perde valore.
Tip: conserva almeno 3 release. Due sono il minimo operativo, tre sono più realistiche. Una release buona, una precedente e una di emergenza riducono molto il rischio di restare bloccati.
Step 6: gestire il drift tra build e produzione
Il drift nasce quando l’ambiente di produzione cambia senza passare dalla pipeline. Può succedere per tanti motivi:
- aggiornamento di una libreria di sistema;
- installazione manuale di un font o di un pacchetto;
- modifica di un file di configurazione fuori Git;
- cambio di versione di Node, Python, ImageMagick o Ghostscript;
- cache persistenti non invalidate.
La contromisura è separare chiaramente tre cose:
- build environment: dove compili e pacchetti;
- runtime environment: dove esegui;
- shared state: dati persistenti, upload, log.
Se la tua app grafica ha bisogno di font specifici per esportare correttamente i PDF, non installarli “a mano” sul server e basta. Versionali o includili nel pacchetto, oppure usa un container o una base immutabile. Altrimenti il prossimo riavvio potrà cambiare il risultato visivo senza che il codice sia cambiato.
Un controllo utile è salvare hash e versioni all’interno dell’artifact:
sha256sum dist.tar.zst > dist.tar.zst.sha256
node --version > build-node-version.txt
npm ls --prod --depth=0 > dependencies.txt
Così puoi confrontare rapidamente due release e capire se il problema è nel codice o nell’ambiente.
Step 7: deploy diretto con rollback, quando ha senso davvero
Il deploy diretto non è sbagliato in assoluto. È solo più adatto a certi contesti.
Ha senso quando:
- il servizio è interno e non critico;
- il server è già l’ambiente di esecuzione e non vuoi duplicarlo in CI;
- il runtime è semplice e stabile;
- la build è veloce e poco dipendente da componenti esterni;
- hai bisogno di iterare rapidamente durante una fase iniziale.
In questo modello, la pipeline può limitarsi a fare SSH sul server e lanciare una sequenza controllata:
stages:
- deploy
deploy:
stage: deploy
image: alpine:3.20
before_script:
- apk add --no-cache openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
script:
- ssh $DEPLOY_USER@$DEPLOY_HOST 'bash -s' < scripts/deploy-and-restart.sh
only:
- main
Lo script sul server può fare checkout, installazione, test e avvio. Ma qui devi accettare il rischio: il server è parte della build, quindi il suo stato influenza il risultato.
Per limitare i danni, almeno:
- fissa le versioni dei pacchetti;
- usa lockfile;
- pulisci le directory temporanee;
- salva una release precedente prima di aggiornare;
- verifica il servizio prima di dichiarare il deploy riuscito.
Step 8: esempio concreto di rollback su deploy diretto
Se vuoi restare sul deploy diretto, il rollback deve essere automatico o quasi. Un pattern utile è questo:
- salvi lo stato corrente;
- esegui il deploy nuovo;
- lanci un health check;
- se fallisce, torni indietro.
Esempio:
#!/usr/bin/env bash
set -euo pipefail
APP_DIR=/srv/grafica/app
BACKUP_DIR=/srv/grafica/backups
STAMP=$(date +%Y%m%d%H%M%S)
mkdir -p "$BACKUP_DIR"
cp -a "$APP_DIR" "$BACKUP_DIR/$STAMP"
cd "$APP_DIR"
git fetch origin main
git reset --hard origin/main
npm ci
npm run build
systemctl restart grafica
if ! curl -fsS http://127.0.0.1:3000/health; then
echo "Health check fallito, rollback"
rsync -a --delete "$BACKUP_DIR/$STAMP/" "$APP_DIR/"
systemctl restart grafica
exit 1
fi
Funziona, ma è più fragile di un artifact immutabile. Il backup può non bastare se il deploy modifica anche database, cache o migrazioni. Per questo il rollback diretto va bene solo se il perimetro del cambiamento è piccolo.
Step 9: cosa scegliere in pratica
La scelta dipende dal livello di rischio e dalla complessità della tua app.
Scegli artifact immutabili se:
- hai export grafici o rendering sensibili a font e librerie;
- vuoi release ripetibili e auditabili;
- hai più ambienti e vuoi lo stesso output ovunque;
- ti serve rollback affidabile in pochi secondi;
- stai già vedendo differenze inspiegabili tra staging e produzione.
Scegli deploy diretto con rollback se:
- il progetto è piccolo o in fase di prototipo;
- il server è già standardizzato e controllato;
- il costo di preparare artifact e storage non vale il guadagno;
- accetti un po’ di drift in cambio di velocità operativa.
In altre parole: l’artifact è la scelta giusta quando la ripetibilità conta più della semplicità iniziale. Il deploy diretto è la scelta giusta quando vuoi muoverti in fretta e il rischio è contenuto.
Step 10: una configurazione consigliata per partire bene
Se il caso d’uso è una web app grafica con export server-side, la configurazione che in genere regge meglio è questa:
- build in GitLab CI dentro un’immagine fissata;
- artifact compresso con checksum;
- deploy via SSH su release directory versionata;
- symlink
currentcome punto stabile; - health check dopo il cambio release;
- conservazione delle ultime 3 o 5 release;
- rollback con semplice ripuntamento del symlink.
Questa combinazione riduce il drift senza complicare troppo la pipeline. E soprattutto rende il problema visibile: se qualcosa si rompe, sai dove guardare.
Conclusione
Il vero vantaggio di GitLab CI non è “fare deploy”. È rendere il rilascio una procedura controllata.
Con artifact immutabili, il server smette di costruire e inizia solo a eseguire. Con il deploy diretto, puoi andare veloce ma devi accettare più variabilità e investire in rollback.
Per progetti grafici, dove font, librerie e output visivo sono delicati, la pipeline minima con artifact è spesso la scelta più solida. Il deploy diretto resta utile, ma solo se il rischio è basso e il rollback è davvero pronto all’uso.
Se vuoi una regola finale molto semplice: quando il risultato deve essere identico, distribuisci un artifact; quando il risultato può tollerare variazioni e ti serve rapidità, fai deploy diretto con rollback.
Commenti (0)
Nessun commento ancora.
Segnala contenuto
Elimina commento
Eliminare definitivamente questo commento?
L'azione non si può annullare.