← tutorial

Tutorial: Come mettere il proprio sito web online

Questa guida affronta operativamente tutte le fasi per mettere online un portfolio personale gestito interamente in autonomia. Il seguente documento è stato sviluppato parallelamente alla progettazioen del mio sito "michelemacellari.me". Pertanto, l'approccio utilizzato nella stesura delle seguente guida è puramente pratico. Dunque, non verranno discussi, da un punto di vista teorico, i pro e i contro di ogni scelta ma mi limiterò ad elencare ciò che ho fatto.

Prima di iniziare

Struttura del sito Web

Browser ↓ DNS (tuonome.tld → IP del server) Server VPS (Ubuntu) ↓ Caddy 2 (container) — reverse proxy + HTTPS automatico ├── / → vetrina statica ├── /progetto-a/* → container A (FastAPI, Node, …) └── /progetto-b/* → container B

Cosa ti serve avere a portata di mano

Fase 1 · Il dominio

1Comprare un dominio

Il dominio è il nome del tuo sito. Costa pochi euro all'anno e si compra da un registrar: Namecheap, Cloudflare Registrar, Aruba, OVH, Porkbun sono tutte scelte ragionevoli. io ho scelto Aruba in quanto mi sembrava il più economico.

Una volta scelto il nome, vai sul registrar (Es. Aruba) cerchi il nome e acquisti (costa pochi euro all'anno).

Costo

Il costo del dominio va dai 4 agli 8 euro all'anno. io per il primo anno ho speso 6,99 euro

Fase 2 · Il server

2Affittare un server VPS

Un VPS (Virtual Private Server) è una macchina virtuale in un datacenter. Per un portfolio io ho preso un server da 4 GB di RAM e 40 GB di SSD sul sito Hetzner (consigliato: costa poco) a 4 euro/mese. Tuttavia, per un portfolio personale è sovradimensionato e consiglio di cercare alternative gratuite come: Oracle Cloud (problema lista di attesa su server di Milano, Torino, Francofort (Prima opzione, tuttavia lista d'attesa troppo lunga).

Crea il server

  1. Registrati su hetzner.com/cloud (richiede verifica documento la prima volta, è normale)
  2. Crea un progetto, poi un nuovo server
  3. Località: Qualsiasi in Europa (più vicino sei meglio è ;) )
  4. Immagine: Ubuntu 24.04 (LTS, supporto lungo)
  5. Tipo: CX22 (2 vCPU shared, 4 GB RAM)
  6. Networking: lascia IPv4 + IPv6 abilitati
  7. SSH key: vedi paragrafo qui sotto
  8. Nome: qualcosa di descrittivo (es. portfolio)

Genera la chiave SSH sul tuo PC

Prima di creare il server, generati una coppia di chiavi SSH sul PC. Su Windows apri PowerShell e digita:

ssh-keygen -t ed25519 -C "tua-email@example.com"

Premi invio ai prompt (puoi anche non mettere passphrase, per semplicità). Verranno creati due file in C:\Users\TUO_UTENTE\.ssh\:

Apri id_ed25519.pub con un editor, copia tutto il contenuto e nel pannello Hetzner aggiungilo come SSH key sul server in fase di creazione.

Perché SSH key e non password

Una password può essere indovinata da un bot. Una chiave ed25519 no: è un numero così grande che non si fa nemmeno a bruteforzarlo. Inoltre niente più password da digitare ogni volta.

Primo accesso al server

Dopo qualche secondo dalla creazione, Hetzner ti mostra l'IP pubblico del server (es. 66.254.114.41). Da PowerShell:

ssh root@66.254.114.41

Al primo accesso conferma il fingerprint. Sei dentro al server!

Fase 3 · DNS e firewall

3Puntare il dominio al server

Il dominio sa di esistere, il server sa di esistere, ma non si conoscono. I record DNS sono l'elenco telefonico che collega tuonome.me all'IP del server.

Vai sul pannello del registrar, nella sezione DNS / Advanced DNS, e aggiungi:

Record DNS minimi
TipoHostValoreTTL
A @ 66.254.114.41 auto
A www 66.254.114.41 auto
A * 66.254.114.41 auto

Il record @ punta il dominio nudo (tuonome.tld), www punta www.tuonome.tld, e il wildcard * punta qualsiasi sottodominio (utile per il futuro). Sostituisci l'IP con quello del tuo server.

La propagazione DNS può richiedere da qualche minuto a qualche ora. Per verificare, dal server:

apt install dnsutils -y
dig tuonome.me +short

Quando ti restituisce l'IP che hai messo, sei a posto.

4Apri solo le porte giuste

Ubuntu include ufw (Uncomplicated Firewall). Sul server:

ufw allow 22       # SSH
ufw allow 80       # HTTP
ufw allow 443      # HTTPS
ufw enable

Tutto il resto è chiuso. Se in futuro un progetto avesse bisogno di altre porte aperte verso l'esterno (raro, di solito basta il reverse proxy), aggiungi una regola.

Fase 4 · Docker + Caddy

5Installare Docker

Docker esegue applicazioni in tutti i linguaggi di programmazione all'interno di "Container". Fondamentalmente, all'interno di questi "Container", c'è tutto il necessario per far funzionare il software(Codice, librerie, impostazioni).

Sul server, lo script ufficiale fa tutto:

curl -fsSL https://get.docker.com | sudo sh
docker --version
docker compose version

6Struttura delle cartelle

Sei sul server (SSH). Tutto quello che segue lo digiti nella stessa sessione SSH che hai aperto al punto 2 (ssh root@TUO_IP). Iniziamo creando l'albero di cartelle che ospiterà il sito.

cd /root
mkdir sito
cd sito
mkdir portfolio
mkdir projects

Verifica con ls di vedere le due cartelle appena create. La struttura finale (con i file che creiamo nei prossimi punti) sarà:

/root/sito/
├── Caddyfile
├── docker-compose.yml
├── portfolio/
│   └── index.html
└── projects/

Crea la pagina HTML di benvenuto

Sul server. Apri l'editor nano per creare il primo file (il portfolio statico):

nano portfolio/index.html

Incolla dentro questo contenuto:

<!doctype html>
<html lang="it">
<head><meta charset="utf-8"><title>Ciao</title></head>
<body><h1>Funziona!</h1></body>
</html>

Per salvare in nano: Ctrl+O, poi Invio per confermare il nome, poi Ctrl+X per uscire.

7Il Caddyfile

Caddy è un reverse proxy moderno che richiede e rinnova automaticamente i certificati HTTPS Let's Encrypt. È la sua killer feature: zero configurazione TLS, basta indicare i domini.

Sul server, mentre sei in /root/sito:

nano Caddyfile

Incolla dentro (sostituendo tuonome.me con il tuo dominio vero):

tuonome.me, www.tuonome.me {
    encode gzip

    handle {
        root * /srv/portfolio
        file_server
    }
}

Salva ed esci (Ctrl+O, Invio, Ctrl+X).

8Il docker-compose.yml

Sul server, sempre nella cartella /root/sito:

nano docker-compose.yml

Incolla dentro:

services:
  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./portfolio:/srv/portfolio:ro
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - web

networks:
  web:

volumes:
  caddy_data:
  caddy_config:

Salva ed esci (Ctrl+O, Invio, Ctrl+X).

Attenzione · indentazione YAML

Il file docker-compose.yml è in formato YAML, che è maniacale sull'indentazione: deve essere fatta con spazi (non tab), e ogni livello rientra di 2 spazi. Se copi-incolli, di solito è tutto a posto. Se errore al primo avvio, verifica gli spazi.

9Primo avvio

Sul server, dalla cartella /root/sito:

docker compose up -d

La prima volta Docker scarica l'immagine di Caddy (qualche secondo). Poi Caddy parte, vede dal Caddyfile che deve gestire tuonome.me, chiede automaticamente il certificato HTTPS a Let's Encrypt e lo riceve. Aspetta 20–30 secondi, poi apri nel browser:

https://tuonome.me

Vedi Funziona! con il lucchetto verde accanto all'URL. Primo traguardo: il tuo sito è online, accessibile da tutto il mondo, con HTTPS.

Per verificare lo stato dal server:

docker compose ps

Deve mostrare il container caddy con stato Up.

Comandi utili Docker Compose (sul server)

docker compose ps — stato dei container attivi.
docker compose logs -f caddy — log Caddy in tempo reale (Ctrl+C per uscire).
docker compose restart caddy — riavvia solo Caddy (serve quando modifichi il Caddyfile: Caddy non rilegge da solo il file).
docker compose down && docker compose up -d — riavvio pulito completo.

Fase 5 · GitHub e workflow Git

10Tre copie del codice: PC, GitHub, server

A questo punto il sito gira sul server, ma esiste solo lì: se per qualsiasi motivo perdi accesso o formatti il server, hai perso tutto. Inoltre, modificare i file via nano in SSH non è il modo più comodo di lavorare. Spostiamo subito il codice in tre posti — PC, GitHub, server — prima di aggiungere progetti veri. Così tutti i progetti che aggiungerai dopo nasceranno già dentro questo flusso.

Diagramma: PC fa push su GitHub, server fa pull da GitHub
se sparisce un posto, recuperi dagli altri due

10.1 — Genera una chiave SSH dedicata a GitHub

Sul PC, apri PowerShell e digita:

ssh-keygen -t ed25519 -f C:\Users\TUO_UTENTE\.ssh\github -C "github"

Premi Invio ai due prompt successivi (senza passphrase, per semplicità). Verranno creati:

Mostra il contenuto della chiave pubblica per copiarlo:

type C:\Users\TUO_UTENTE\.ssh\github.pub

Seleziona tutto l'output (da ssh-ed25519 fino a fine riga) e copialo.

10.2 — Aggiungi la chiave su GitHub

  1. Vai su github.com e accedi
  2. In alto a destra clicca sulla tua icona → Settings
  3. Nel menù di sinistra: SSH and GPG keysNew SSH key
  4. Title: PC personale (un nome qualsiasi). Key type: Authentication Key
  5. Key: incolla la chiave pubblica che hai copiato
  6. Clicca Add SSH key

10.3 — Configura SSH sul PC

Sul PC (PowerShell), crea (o modifica) il file C:\Users\TUO_UTENTE\.ssh\config:

notepad C:\Users\TUO_UTENTE\.ssh\config

(Se chiede di crearlo nuovo, accetta.) Incolla dentro:

Host github.com
    HostName github.com
    User git
    IdentityFile C:/Users/TUO_UTENTE/.ssh/github

Attenzione: su Windows il path deve essere assoluto e con slash /, non backslash. Salva e chiudi Notepad. Testa:

ssh -T git@github.com

Alla prima esecuzione conferma il fingerprint con yes. Deve rispondere: "Hi TUO_USERNAME! You've successfully authenticated, but GitHub does not provide shell access."

10.4 — Crea il repository su GitHub

  1. Su GitHub, in alto a destra: + → New repository
  2. Repository name: sito (o come preferisci)
  3. Visibilità: Private (anche se non hai segreti adesso, è una buona abitudine)
  4. NON spuntare "Add a README", "Add .gitignore", "Choose a license" — partiamo vuoti
  5. Clicca Create repository

Nella pagina che appare, in alto, segna l'URL SSH: git@github.com:TUO_USERNAME/sito.git — lo useremo subito.

10.5 — Configura l'identità Git sul PC

Sul PC (PowerShell), una sola volta in vita tua:

git config --global user.name "Tuo Nome"
git config --global user.email "tua-email@example.com"

Senza questa configurazione, il primo commit fallirà con "Author identity unknown".

10.6 — Scarica la cartella dal server al PC

Sul PC (PowerShell), scegli dove tenere il sito (es. C:\Sito):

mkdir C:\Sito
cd C:\Sito
scp -r root@TUO_IP:/root/sito/. .

Sostituisci TUO_IP con l'IP del tuo server. Il comando scp (Secure Copy) usa la chiave SSH già configurata e copia ricorsivamente tutto il contenuto di /root/sito/ dentro C:\Sito. Verifica con dir: devi vedere Caddyfile, docker-compose.yml e la cartella portfolio (al momento niente projects: la aggiungeremo dopo).

10.7 — Crea il file .gitignore

Sul PC, nella cartella C:\Sito:

notepad .gitignore

(Se Notepad chiede di crearlo, accetta.) Incolla dentro:

__pycache__/
*.pyc
.DS_Store
node_modules/
.env
*.log

Salva e chiudi. Questo file dice a Git quali file ignorare (cache, log, segreti) per non finire mai su GitHub.

10.8 — Inizializza il repo e fai il primo push

Sul PC (PowerShell), sempre dentro C:\Sito:

git init
git remote add origin git@github.com:TUO_USERNAME/sito.git
git add .
git commit -m "primo commit"
git branch -M main
git push -u origin main

Cosa fa ognuno:

Apri la pagina GitHub del repo nel browser: ora vedi tutti i tuoi file lì. Terzo traguardo.

Sicurezza · cosa non committare MAI

Password, chiavi API, token, file .env, chiavi SSH private. Anche se il repo è privato adesso, lo storico Git conserva tutto: se per errore committi un segreto, NON basta toglierlo in un commit successivo — devi ruotare la chiave dal provider perché va considerata compromessa. Per questo serve .gitignore.

Fase 6 · Server collegato a GitHub

11Il server fa pull da GitHub

Adesso il codice esiste su PC e su GitHub, ma sul server è ancora "scollegato" — è una copia che non sa di GitHub. Vogliamo che il server sappia tirarsi giù gli aggiornamenti via git pull. Serve una deploy key dedicata, in sola lettura.

11.1 — Genera una deploy key sul server

Sul server (SSH):

ssh-keygen -t ed25519 -f ~/.ssh/github_deploy -N ""

Il flag -N "" dice "senza passphrase" (serve perché il server faccia i pull non interattivi). Crea due file in /root/.ssh/: github_deploy (privata) e github_deploy.pub (pubblica).

11.2 — Aggiungi la chiave come Deploy Key su GitHub

Mostra il contenuto della chiave pubblica:

cat ~/.ssh/github_deploy.pub

Seleziona tutto l'output e copialo. Poi:

  1. Vai su GitHub → repo sitoSettings (in alto a destra del repo, non del profilo)
  2. Menù di sinistra: Deploy keysAdd deploy key
  3. Title: server hetzner
  4. Key: incolla la chiave pubblica
  5. NON spuntare "Allow write access" — il server deve solo leggere
  6. Clicca Add key

11.3 — Configura SSH sul server

Sul server (SSH):

nano ~/.ssh/config

Incolla:

Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/github_deploy
    IdentitiesOnly yes

Salva ed esci. Sistema i permessi e testa:

chmod 600 ~/.ssh/config
ssh -T git@github.com

Deve rispondere: "Hi TUO_USERNAME/sito! You've successfully authenticated…". Nota: per le deploy key il messaggio cita anche il nome del repo.

11.4 — Sostituisci la cartella con un clone Git

Sul server (SSH). Adesso vogliamo che /root/sito non sia più una cartella "qualsiasi" ma un clone Git del repo. Prima fermiamo i container e mettiamo da parte la cartella attuale come backup:

cd /root/sito
docker compose down
cd ~
mv ~/sito ~/sito-backup
git clone git@github.com:TUO_USERNAME/sito.git
cd sito
docker compose up -d

Cosa abbiamo fatto:

Apri nel browser https://tuonome.me per verificare che la vetrina risponda ancora. Quando sei tranquillo, elimina il backup:

rm -rf ~/sito-backup

11.5 — Crea l'alias deploy

Sul server (SSH):

nano ~/.bashrc

Vai in fondo al file (Ctrl+End) e aggiungi:

alias deploy='cd ~/sito && git pull && docker compose up -d --build'

Salva ed esci. Ricarica la configurazione della shell:

source ~/.bashrc

Da adesso, ogni volta che sul server digiti deploy, vengono eseguite in sequenza: vai in ~/sito, scarica gli aggiornamenti da GitHub, ricostruisci e riavvia i container. Una sola parola per pubblicare le modifiche.

Fase 7 · Primo progetto eseguibile

12Aggiungere un'applicazione

Adesso che il workflow Git è in piedi, non tocchiamo più il server a mano. Tutti i progetti (questo primo e tutti i successivi) nascono sul PC, vengono pushati su GitHub e arrivano sul server con un solo comando deploy. L'esempio assume una app Python/FastAPI, ma il pattern è identico per Node, Go, Rust o qualsiasi altra cosa che parli HTTP.

12.1 — Crea la cartella del progetto

Sul PC, dentro C:\Sito:

mkdir projects
mkdir projects\mioprogetto

Dentro projects\mioprogetto\ metti il codice della tua app (qui non spiego come scrivere una FastAPI: assumo che tu abbia già il codice). Quello che serve aggiungere SEMPRE è un file Dockerfile.

12.2 — Crea il Dockerfile

Sul PC, apri il tuo editor (VS Code, Notepad) e crea il file C:\Sito\projects\mioprogetto\Dockerfile (senza estensione) con dentro (esempio FastAPI; cambia in base al tuo stack):

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app.api:app", \
     "--host", "0.0.0.0", \
     "--port", "8000", \
     "--proxy-headers", \
     "--forwarded-allow-ips=*", \
     "--root-path", "/mioprogetto"]

12.3 — Aggiungi il servizio al docker-compose.yml

Sul PC, apri C:\Sito\docker-compose.yml e aggiungi il blocco mioprogetto: sotto al servizio caddy (stessa indentazione). Il file diventa:

services:
  caddy:
    # … blocco già esistente …

  mioprogetto:
    build: ./projects/mioprogetto
    restart: unless-stopped
    networks:
      - web

networks:
  web:

volumes:
  caddy_data:
  caddy_config:

12.4 — Aggiungi la regola al Caddyfile

Sul PC, apri C:\Sito\Caddyfile e modificalo così (le righe nuove vanno prima del blocco handle):

tuonome.me, www.tuonome.me {
    encode gzip

    handle_path /mioprogetto/* {
        reverse_proxy mioprogetto:8000
    }

    redir /mioprogetto /mioprogetto/ 308

    handle {
        root * /srv/portfolio
        file_server
    }
}

handle_path rimuove il prefisso /mioprogetto prima di girare la richiesta al container (l'app dentro pensa di vivere alla radice). La riga redir aggiunge automaticamente lo slash finale se l'utente arriva su /mioprogetto senza slash.

12.5 — Aggiungi una card al portfolio

Sul PC, apri C:\Sito\portfolio\index.html e aggiungi un link al progetto, anche solo:

<a href="/mioprogetto/">Apri il mio progetto</a>

12.6 — Push e deploy

Sul PC (PowerShell), dentro C:\Sito:

git add .
git commit -m "aggiunto primo progetto"
git push

Sul server (SSH):

deploy

Il server scarica le novità da GitHub, costruisce l'immagine del progetto (la prima volta scarica Python + dipendenze: 1–2 minuti) e riavvia tutto. Quando finisce, apri nel browser:

https://tuonome.me/mioprogetto/

L'applicazione risponde. Secondo traguardo.

Tre comandi diversi per tre cose diverse (sul server)

L'alias deploy esegue docker compose up -d --build, che copre il caso più comune. Quando in futuro vorrai agire più chirurgicamente, ricordati che:
Modifica al codice di un progettodocker compose up -d --build (ricostruisce l'immagine).
Modifica al Caddyfiledocker compose restart caddy (Caddy non ricarica automaticamente).
Modifica al docker-compose.ymldocker compose up -d (ricrea solo ciò che è cambiato).
Modifica alla vetrina statica HTML → niente, Caddy serve i file via bind mount.

Fase 8 · Vita quotidiana

13Il workflow di tutti i giorni

Modificare codice esistente

Sul PC, dentro C:\Sito:

  1. Modifichi i file che vuoi con il tuo editor (VS Code, Notepad, qualsiasi cosa)
  2. Apri PowerShell nella cartella, e digita:
git add .
git commit -m "descrizione cambiamento"
git push

Poi sul server (SSH):

deploy

30 secondi e la modifica è online. Apri il browser per verificare (con Ctrl+Shift+R se sospetti la cache).

Tre comandi Git e basta

Tutto il workflow Git si riduce a tre verbi.
git add . — prende tutte le modifiche che hai fatto e le prepara per essere "fotografate".
git commit -m "..." — scatta la fotografia con un messaggio che descrive cos'è cambiato (sarà la tua memoria futura, scrivi qualcosa di sensato).
git push — manda la fotografia su GitHub.

Aggiungere un altro progetto in futuro

Stesso identico flusso del primo progetto, riassunto:

Sul PC, dentro C:\Sito:

  1. Crea cartella projects/nuovoprogetto/ con codice + Dockerfile
  2. Aggiungi il blocco servizio in docker-compose.yml
  3. Aggiungi handle_path /nuovoprogetto/* { reverse_proxy nuovoprogetto:PORTA } + redir al Caddyfile
  4. Aggiungi card/link in portfolio/index.html
  5. Da PowerShell: git add . && git commit -m "…" && git push

Sul server: deploy.

Tornare indietro se hai rotto qualcosa

Sul server (SSH):

cd ~/sito
git log --oneline           # vedi i commit recenti, copia l'hash che vuoi
git reset --hard HASH       # torna a quel commit (sostituisci HASH)
docker compose up -d --build

In 30 secondi sei alla versione di prima. Questo è il bello di tenere tutto in Git: ogni commit è un punto di ripristino.

Diagramma: PC fa push su GitHub, server fa pull da GitHub
il flusso che ripeterai ogni volta: modifica sul PC → push → deploy sul server
video: lo stesso flusso, eseguito passo per passo
Quanto costa tutto questo

Dominio: ~10–15 €/anno. Server Hetzner CX22: ~4 €/mese (~50 €/anno). Tutto il resto (HTTPS, GitHub repo privato, monitoring base) è gratis. Totale: ~60–70 €/anno per un portfolio personale tuo al 100%, espandibile senza limiti aggiungendo container.

E adesso

Hai un'infrastruttura che regge i prossimi cinque progetti senza cambiare nulla. La prossima volta che vuoi aggiungere qualcosa, basta una cartella in projects/, una riga in docker-compose.yml, un blocco in Caddyfile, un push, un deploy. Buon lavoro.