Loading theme
~/saverio

Setup del server e primo deploy

9 feb 2025· agg. 22 feb 2026· 9 min
hetznercoolifydockerdeployment

Setup del server e primo deploy

Cosa otterrai: un server Hetzner con security hardening, Docker, Coolify e una connessione validata. Poi pushi codice, Coolify lo builda, e apri un URL per vedere la tua app. Nessun dominio richiesto. Per dominio e SSL, vedi la sezione successiva.

Prerequisito: Lockdown con Tailscale e firewall. Falla per prima. SSH al server via Tailscale, poi esegui il setup qui sotto.

Setup del server

Chiave SSH

Genera una chiave ED25519 (o usane una esistente):

ssh-keygen -t ed25519 -f ~/.ssh/hetzner-platform

Copia la chiave pubblica e aggiungila a Hetzner prima di creare il server:

cat ~/.ssh/hetzner-platform.pub

Incollala in Hetzner → + Add SSH key → salva.


Crea il server Hetzner

Quando crei il server:

OpzioneValore
TipoRegular Performance, x86 (AMD), CPX41 (8 vCPU, 16 GB RAM)
LocationFalkenstein o Norimberga
NetworkingIPv4 + IPv6 pubblici
SSH KeysAggiungi la tua (richiesta)
FirewallsSalta, oppure creane uno con TCP 2222, 80, 443 (lo script usa 2222 per SSH; il firewall Hetzner di default permette solo la 22)
Volumes, Backups, Cloud configSalta
Nameplatform-1 o platform-prod

Esegui lo script di setup

Connettiti come root (porta 22 prima che lo script venga eseguito):

ssh -i ~/.ssh/hetzner-platform -o IdentitiesOnly=yes root@YOUR_SERVER_IP

Copia ed esegui lo script:

# Dalla tua macchina locale
scp -i ~/.ssh/hetzner-platform -o IdentitiesOnly=yes scripts/server-setup.sh root@YOUR_SERVER_IP:/root/server-setup.sh

# Sul server
chmod +x /root/server-setup.sh
/root/server-setup.sh

IdentitiesOnly=yes evita "too many authentication failures" quando hai molte chiavi nell'ssh-agent.

Cosa fa lo script

AreaDettagli
SicurezzaUtente deploy con sudo, SSH sulla porta 2222, auth solo per chiave, login root con chiave (richiesto da Coolify), UFW, Fail2ban, unattended updates
DockerCE + Compose, deploy nel gruppo docker, log rotation (10 MB × 3 file)
CoolifyInstallato alla fine; permessi /data/coolify impostati; chiave SSH verificata
SistemaTimezone Europe/Rome, 2 GB di swap, zsh + Oh My Zsh, tool (htop, ncdu, fzf, ripgrep, eza, bat, fd, jq, tree)

Verifica prima di chiudere root

Apri un nuovo terminale e prova:

ssh -i ~/.ssh/hetzner-platform -o IdentitiesOnly=yes -p 2222 deploy@YOUR_SERVER_IP

Se funziona, sei a posto. Se no, sistemalo dalla sessione root ancora aperta.


Hetzner Cloud Firewall (richiesto)

Docker bypassa UFW. Le porte Coolify (8000, 6001, 6002) si possono restringere solo via Hetzner Cloud Firewall.

Aggiungi le regole inbound:

PortaProtocolloSorgenteScopo
2222TCPAnySSH
80TCPAnyHTTP
443TCPAnyHTTPS
8000TCPTuo IP + IP GitHubDashboard Coolify (gli IP GitHub servono per webhook / auto deploy)
6001TCPTuo IPCoolify real-time
6002TCPTuo IPTerminale Coolify

Rimuovi la porta 22 dopo il setup. Restringi 8000/6001/6002 al tuo IP per sicurezza. Per l'auto deploy on push, aggiungi i range IP webhook GitHub (vedi hooks) sulla porta 8000. Vedi server-setup-quick.md per i CIDR esatti.


Setup iniziale di Coolify

Accedi alla dashboard su http://YOUR_SERVER_IP:8000 (Coolify 4.x usa la porta 8000).

Crea il tuo account

Al primo caricamento, Coolify ti chiede di creare l'account admin root.

Setup iniziale Coolify: form di creazione account

Riempi nome, email e una password forte (min 8 caratteri, maiuscola, minuscola, numero, simbolo).

Aggiungi il tuo server

Subito dopo la creazione dell'account, Coolify potrebbe mostrare un wizard. Saltalo e aggiungi il server a mano.

Coolify: Choose Server Type, This Machine, Remote Server, Hetzner Cloud

Step A: aggiungi la tua chiave SSH

  1. SettingsKeys & TokensSSH Keys+ New
  2. Incolla la tua chiave privata: cat ~/.ssh/hetzner-platform (output completo, comprese le righe -----BEGIN e -----END)
  3. Salva

Step B: aggiungi il server

  1. Servers+ NewRemote Server
  2. Compila:
    • Name: platform-1 (o un nome qualunque)
    • IP Address/Hostname: l'IP del tuo server
    • Advanced Connection SettingsPort: 2222, User: root
  3. Private Key: seleziona la chiave che hai aggiunto
  4. Click su Validate Connection

Se la validazione va a buon fine, il server è pronto per i deploy.

Perché root? Coolify fa SSH all'host per scrivere config di deploy (.env, docker-compose.yaml) sotto /data/coolify/applications/. Internamente usa sudo echo '...' | tee /path, ma tee gira con i permessi dell'utente che invoca. Con un utente non-root, la scrittura fallisce anche con sudo. Usare root lo evita. L'utente deploy resta disponibile per le tue sessioni SSH quotidiane.


Primo deploy

Dopo il setup del server hai:

  • Dashboard Coolify su http://YOUR_SERVER_IP:8000
  • Coolify connesso al server come root (porta SSH 2222)
  • Utente deploy per le tue sessioni SSH quotidiane

Prepara il codebase

Monorepo e pnpm

Questa repo usa pnpm workspaces. Alla root ti serve:

  • pnpm-workspace.yaml con packages: ["apps/*", "packages/*"]
  • package.json di root con "packageManager": "[email protected]"
  • onlyBuiltDependencies: [sharp] in pnpm-workspace.yaml (pnpm 10 blocca i lifecycle script di default; sharp serve per le immagini Next.js)

Scaffolda l'app blog

mkdir -p apps
pnpm create next-app@latest apps/blog

Rispondi: TypeScript, ESLint/Biome, Tailwind, directory src/, App Router.

Poi:

pnpm install

Aggiungi a pnpm-workspace.yaml:

onlyBuiltDependencies:
  - sharp

Esegui di nuovo pnpm install. Rimuovi apps/blog/pnpm-lock.yaml se presente (in monorepo si usa solo il lockfile di root). Esegui sempre pnpm install dopo aver aggiunto dipendenze e committa pnpm-lock.yaml; Coolify usa --frozen-lockfile e fallisce se il lockfile non è in sync. Vale per qualunque modifica a apps/*/package.json: aggiungere @radix-ui/react-scroll-area, rehype-slug o qualunque altra dep senza eseguire pnpm install alla root romperà il prossimo deploy.

Configurazione Next.js

Non usare output: "standalone" con Nixpacks. Il build pack fa una COPY finale che sovrascrive l'output di build, quindi server.js manca a runtime. Usa il next start di default.

Verifica in locale

pnpm --filter blog dev

Apri http://localhost:3000. Testa la produzione: pnpm --filter blog build poi pnpm --filter blog start.


Connetti Coolify a GitHub

Per una repo privata, Coolify ha bisogno di una GitHub App.

Aggiungi il source GitHub App

  1. Sources+ AddGitHub App
  2. Name: es. platform-coolify (evita il nome riservato coolify)
  3. Organization: lascialo vuoto per account personali
  4. Click su Continue

Coolify ti reindirizza a GitHub per creare l'app. Completa il flow e installa l'app sul tuo account. Scegli Only select repositories e seleziona platform.

Registra il webhook

Tornato in Coolify, apri Sources → la tua GitHub App. Vedrai:

  • Webhook Endpoint: http://YOUR_SERVER_IP:8000
  • Bottone Register Now
  • Un warning: "You must complete this step before you can use this source!"

Click su Register Now. Questo dice a GitHub dove mandare gli eventi di push e PR. Lascia Mandatory e Preview Deployments spuntati.

Credenziali GitHub App (se usi setup manuale): riempi Client Secret e Webhook Secret da GitHub → Developer settings → GitHub Apps → la tua app. Organization resta vuoto per account personali.

Permessi GitHub App (su GitHub): Repository permissions → Contents (Read-only), Metadata (Read-only). Subscribe to events → abilita Push (richiesto per auto deploy). Opzionalmente Pull requests per i preview deployment.


Crea e fai deploy dell'applicazione

Crea un progetto

Projects+ Add (o New Project). Chiamalo platform, poi Continue.

Aggiungi l'applicazione

Nel progetto, + Add ResourceApplication.

Step 1: repository e build pack

CampoValore
Repositoryplatform (selezionalo dal dropdown, poi Load Repository)
Branchmaster (o main)
Build PackNixpacks
Base Directory/
Port3000
Is it a static site?No

Click su Continue.

Step 2: comandi di build e start

CampoValore
Install Command(lascia vuoto: auto-detected via packageManager)
Build Commandpnpm --filter blog build
Start Commandpnpm --filter blog start
Watch Paths** (oppure apps/blog/** se vuoi i deploy solo quando cambia il codice del blog)

Base Directory / è richiesto: Nixpacks ha bisogno di pnpm-lock.yaml e pnpm-workspace.yaml alla root della repo. Build e start puntano al pacchetto blog via --filter blog. Usa Watch Paths ** così i push che cambiano pnpm-lock.yaml o config di root scatenano comunque un deploy.

Abilita Auto Deploy

ConfigurationAdvanced → General → abilita Auto Deploy. Senza, i push non scateneranno deploy.

Deploy

Click su Save poi Deploy. Coolify builda l'immagine, avvia il container e assegna un URL (es. random-name.YOUR_IP.sslip.io). Aprilo per verificare che l'app sia live.

L'URL lo trovi in ConfigurationDomains o nel tab Links.


Troubleshooting

Se l'app è in stato Exited, controlla Logs per errori.

ErroreFix
Permission denied scrivendo .env o docker-compose.yamlCoolify deve fare SSH come root. Controlla: (1) Server User è root in Coolify, (2) PermitRootLogin prohibit-password nella config SSH. Lo script di setup gestisce entrambi.
ERR_PNPM_NO_LOCKFILEBase Directory deve essere /. Con apps/blog, il lockfile manca dal contesto di build.
ERR_PNPM_OUTDATED_LOCKFILEpnpm-lock.yaml non è in sync con package.json (es. dep aggiunte a apps/blog/package.json senza eseguire pnpm install). Esegui pnpm install alla root della repo, committa e pusha pnpm-lock.yaml.
pnpm: command not foundAggiungi "packageManager": "[email protected]" al package.json di root. Nixpacks usa Corepack per installare pnpm.
onlyBuiltDependencies mancante per sharpAggiungi onlyBuiltDependencies: [sharp] a pnpm-workspace.yaml, poi pnpm install di nuovo.
Errore cache Docker BuildKit (parent snapshot does not exist)Sul server: sudo docker builder prune -af, poi riprova il deploy.
Push che non scatena deployControlla: (1) GitHub → Developer settings → GitHub Apps → la tua app → Advanced → Recent Deliveries (verde = webhook OK); (2) Auto Deploy abilitato in Coolify → Advanced; (3) Watch Paths: apps/blog/** scatena solo sui cambi del blog; usa ** per qualunque push; (4) Se il webhook è OK ma niente deploy, click su Redeploy a mano.
Caricamento infinito su Brave (o altri browser privacy)Brave Shields blocca script di terze parti (es. Clerk da clerk.com). Se ClerkProvider avvolge tutta l'app, la pagina si pianta. Avvolgi ClerkProvider solo intorno alle route admin/auth, non nel root layout. Vedi ClerkProvider e browser privacy nella sezione Autenticazione.

Appendice: tool installati

ToolScopo
htopMonitor processi interattivo
ncduUso disco visuale
fzfFuzzy finder
ripgrepRicerca testo veloce
ezals moderno
batcat con syntax highlighting
fdFile finder veloce
jqParsing JSON
treeAlbero di directory

Alias: dcdocker compose, dps → stato dei container, fdfdfind (rename Ubuntu).

Vedi server-setup-quick.md per riferimento rapido e troubleshooting.


Cursor rules e skill (opzionali)

Se usi Cursor (o Claude Code), imposta rules e skill prima o dopo lo scaffolding.

Rules (.cursor/rules/*.mdc): convenzioni a livello progetto, sempre disponibili. Usa description, alwaysApply o globs nel frontmatter.

Skill (.cursor/skills/ o ~/.cursor/skills/): istruzioni specifiche per task, caricate quando rilevanti. Installale via:

npx skills add https://github.com/vercel-labs/next-skills --skill next-best-practices

Skills installer: scegli a quali agent installare la skill

Sfoglia skills.sh per altro. Le skill specifiche del progetto vanno in .cursor/skills/ o .claude/skills/ con un file SKILL.md.


Gitignore e env var (opzionale)

.gitignore di root: node_modules/, .next/, out/, .agents/, .env, .env.local, .env*.local.

Sviluppo locale: cp apps/blog/.env.example apps/blog/.env.local, edita coi valori reali. Mai committare.

Coolify: aggiungi le env var nella UI Coolify per ogni app. Vengono iniettate a runtime. Per app con auth (es. Better Auth), imposta NEXT_PUBLIC_SITE_URL e BETTER_AUTH_URL al tuo URL di produzione (es. https://your-domain.com). Le var NEXT_PUBLIC_* sono embeddate al build time, quindi rifai il deploy dopo averle aggiunte.