in produzione

sGTM Self-Hosted: come abbiamo eliminato Stape

La storia di come un consulente analytics e un'AI hanno messo in piedi un Server-Side Google Tag Manager su un VPS da €0 extra al mese, bevendo caffè virtuale e litigando con Docker.

Giacomo Galanti + Claude Code
Stack: Docker · Nginx · Let's Encrypt
Tempo: ~1 giorno
// 01 — il punto di partenza

Il Problema

Se lavori con il tracking server-side, conosci la scena: apri la dashboard di Stape, vedi la fattura del mese, e ti chiedi se forse non stai pagando troppo per tre container Docker che girano su una VM.

Le alternative? GCP managed a €120-400/mese. Addingwell a €90-360/mese. Stape stesso da €20 a €200/mese. Tutti servizi ottimi, per carità. Ma se hai già un VPS che gira 24/7 con CPU e RAM a disposizione, la domanda diventa inevitabile:

La domanda

«Ho già un VPS. Perché non posso far girare sGTM da solo?»

La risposta breve è: puoi. La risposta lunga è questa pagina.

Così Giacomo ha aperto un terminale, ha chiamato il suo collega preferito (un'AI di nome Claude Code), e gli ha detto: «Senti, ci mettiamo su sGTM da soli. Tu sai Docker, io so GTM. Andiamo.»

// 02 — la soluzione

La Soluzione

Abbiamo trovato un'implementazione di riferimento eccellente di giovaniortolani/local-sgtm — un progetto open source che fa esattamente quello che volevamo. L'abbiamo clonato, studiato, e adattato al nostro VPS.

L'architettura

Tre container Docker, un reverse proxy Nginx, un certificato SSL. Fine.

Browser / GA4
hit events
Let's Encrypt SSL
sgtm.galanti.digital
Nginx VPS
reverse proxy :443
gtm-nginx
:8888 / :8889
gtm-live
:9229
gtm-preview
:9228
Docker Network: gtm-network (bridge)

Il flusso è lineare: il browser manda gli hit a sgtm.galanti.digital, Nginx del VPS termina SSL e fa proxy verso il container gtm-nginx, che a sua volta smista tra il server live e quello di preview.

Il deploy

Letteralmente tre comandi:

terminal
$ git clone https://github.com/giovaniortolani/local-sgtm.git $ cd local-sgtm && cp .env.example .env # Inserisci il tuo CONTAINER_CONFIG nel .env $ docker compose up -d ✓ ssl-init Created ✓ gtm-preview Started ✓ gtm-live Started ✓ gtm-nginx Started

Claude Code si è occupato della configurazione Nginx, del certificato Let's Encrypt, e di tutti quei dettagli che ti fanno perdere due ore se li fai a mano. Io mi sono occupato del container GTM, delle variabili, e di verificare che gli hit arrivassero correttamente.

Teamwork, nel senso letterale del termine. Solo che uno dei due è un'intelligenza artificiale.

// 03 — i problemi (e come li abbiamo risolti)

I Problemi

Perché nessun deploy va liscio la prima volta. Mai. Neanche quando hai un'AI che ti aiuta. Specialmente quando hai un'AI che ti aiuta, perché lei è ottimista per natura.

Conflitto porta 443
Il docker-compose originale voleva bindare la porta 443. Peccato che sul nostro VPS la porta 443 è già occupata da Nginx che serve altri siti. Fix: commentata la riga nel docker-compose e lasciato che Nginx del VPS faccia da reverse proxy.
host.docker.internal = timeout
Su macOS host.docker.internal funziona magicamente. Su Linux? Timeout infinito. Il server live non riusciva a raggiungere il preview server. Fix: cambiato PREVIEW_SERVER_URL in https://gtm-nginx:8889 per usare la rete Docker interna.
Auth basic da disabilitare
Per sicurezza, tutte le nostre pagine web hanno HTTP Basic Auth. Ma sGTM riceve hit automatici da GA4 — il browser non può chiedere username e password a un pixel di tracciamento. Fix: auth disabilitata nella location Nginx dedicata a sGTM.

Le modifiche al docker-compose

docker-compose.yml (diff)
# Nginx container - porte modificate ports: - '${PORT_TAGGING_SERVER}:8888' # Live server # - '443:8888' <-- DISABILITATO: conflitto con Nginx VPS - '${PORT_PREVIEW_SERVER}:8889' # Preview server # GTM Live - preview URL modificato environment: PREVIEW_SERVER_URL: 'https://gtm-nginx:8889' # Era: 'https://host.docker.internal:${PORT_PREVIEW_SERVER}' NODE_TLS_REJECT_UNAUTHORIZED: '0'

La configurazione Nginx

nginx sgtm.galanti.digital
server { listen 443 ssl; server_name sgtm.galanti.digital; ssl_certificate /etc/letsencrypt/live/sgtm.galanti.digital/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/sgtm.galanti.digital/privkey.pem; # Niente auth_basic - sGTM riceve hit automatici location / { proxy_pass http://localhost:8888; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }
Nota per chi replica

Se usate Linux (non macOS), non fidatevi di host.docker.internal. Usate sempre i nomi dei container nella rete Docker. Vi risparmierà un'ora di curl -v e imprecazioni assortite.

// 04 — performance testing

Performance Testing

«Ok funziona, ma regge il carico?» — la domanda che ogni consulente si fa prima di puntare il sGTM di un cliente al proprio VPS.

Abbiamo fatto i compiti. Load test con wrk, curl flood, monitoraggio Docker stats esportato in CSV. Claude Code ha scritto gli script, io ho analizzato i risultati.

load test
# wrk - 4 threads, 100 connessioni, 30 secondi $ wrk -t4 -c100 -d30s https://sgtm.galanti.digital/healthy Requests/sec: 2847.33 Transfer/sec: 412.18KB Latency avg: 34.91ms # docker stats durante il test $ docker stats --no-stream gtm-live CPU: 23% MEM: 187MiB gtm-preview CPU: 4% MEM: 142MiB gtm-nginx CPU: 1% MEM: 8MiB
~12 KB
per hit (IN+OUT)
12 GB
per 1M hit (banda)
<35ms
latenza media
23%
CPU max sotto carico
Verdetto

Un VPS KVM 4 regge tranquillamente un sGTM in produzione per la maggior parte dei siti. Per volumi molto alti si può passare a un KVM 8 senza cambiare nulla dell'architettura.

// 05 — i numeri

I Numeri

Ecco il confronto che nessun vendor vi farà mai vedere. Spoiler: noi siamo l'ultima riga.

Soluzione Costo/mese Controllo Velocità Vendor lock-in
Stape €20 – 200 Limitato Buona
GCP Managed (App Engine / Cloud Run) €120 – 400 Medio Buona
Tag Manager Server-Side (Google) €0 – 500+ Alto Variabile Sì (GCP)
Addingwell €90 – 360 Limitato Buona
Self-hosted (noi) ~€0 extra Totale Ottima No

Il «~€0 extra» non è clickbait. Il VPS lo pagavamo già per altri servizi. sGTM usa meno del 25% della CPU disponibile. Non abbiamo aggiunto nessun costo infrastrutturale. Zero.

E se domani Stape cambia i prezzi, o GCP decide di aumentare le tariffe di Cloud Run? Noi non ci accorgiamo neanche. Il nostro sGTM gira su ferro nostro.

// 06 — cosa abbiamo imparato

Cosa Abbiamo Imparato

  • Su un VPS KVM 4 puoi far girare sGTM in produzione. Non servono macchine dedicate, non servono cluster Kubernetes. Tre container Docker e un reverse proxy.
  • Docker rende il deploy banale. docker compose up -d e sei live. Aggiornamento? docker compose pull && docker compose up -d. Fatto.
  • Il networking Docker su Linux ha le sue insidie. host.docker.internal non funziona come su macOS. Bisogna usare i nomi dei container nella rete bridge.
  • Claude Code è stato fondamentale. Debugging delle configurazioni Docker, scrittura dei config Nginx, load testing, analisi dei risultati. Non è stato un tool — è stato un collega che sa leggere i log più velocemente di me.
  • Feature Stape-like si possono replicare. Cookie Keeper, Custom Loader (anti ad-block), CDN proxy — sono tutte cose sviluppabili in-house. La piattaforma è nostra, le regole le facciamo noi.

Prossimo step

Roadmap

Prossima fase: Cookie Keeper, Custom Loader e funzionalità anti ad-block. Tutto sviluppato in-house, sullo stesso VPS, senza dipendere da nessun vendor.

Il bello di questo progetto? Non è finito. È un'infrastruttura viva, che evolve. Possiamo aggiungere custom loaders, proxy per i cookie first-party, endpoint personalizzati. Tutto senza chiedere il permesso a nessun vendor.

E la prossima volta che qualcuno ci chiede «ma voi usate Stape?», la risposta sarà: «No. Facciamo meglio.»

Ti interessa un sGTM self-hosted?

Se vuoi eliminare i costi ricorrenti del server-side tracking senza rinunciare al controllo, parliamone.

← galanti.digital