In questa lezione impari a
- distinguere l'ambiente di sviluppo da quello di produzione
- tenere i segreti fuori dal codice con le variabili d'ambiente
- avviare l'applicazione con un server WSGI vero
- pubblicare l'applicazione online
- controllare un'applicazione con la lista di sicurezza prima di pubblicarla
La bacheca funziona sul vostro PC. Ma "funziona sul mio computer" non basta: un server pubblico è raggiungibile da chiunque, comprese persone che cercano di romperlo. Oggi la prepariamo per il mondo vero.
Il kit della lezione
Avviatelo come al solito con python app.py: si ferma con un errore. È voluto! Leggete il messaggio: manca il file .env. Lo sistemiamo nel primo paragrafo.
Sviluppo e produzione
| Sviluppo (il vostro PC) | Produzione (il server pubblico) | |
|---|---|---|
| Chi lo usa | voi | tutti, anche malintenzionati |
| Debug | acceso: errori dettagliati nel browser | spento: mai mostrare il codice |
| Server | app.run() (semplice, lento) |
un server WSGI (Waitress, Gunicorn) |
| Segreti | possono essere di prova | lunghi, casuali, fuori dal codice |
| Errori | li vedete voi nel terminale | pagina gentile per l'utente, dettagli nei log |
Perché il debug acceso è pericoloso? Con
debug=True, quando c'è un errore Flask mostra una pagina con il codice e una console Python interattiva nel browser: chi la raggiunge può eseguire comandi sul server. Su un server pubblico equivale a lasciare le chiavi nella porta.
1. I segreti fuori dal codice: il file .env
SECRET_KEY e API_KEY non devono stare in app.py: il codice finisce su GitHub, viene consegnato, copiato. Li mettiamo in un file a parte, .env, che non si condivide mai.
- Copiate
.env.esempioe rinominate la copia in.env(con il punto davanti, senza altro). - Generate una chiave casuale:
python -c "import secrets; print(secrets.token_hex(32))"
- Incollatela in
.env, e fate lo stesso con una seconda chiave per l'API:
SECRET_KEY=3f9a1c…(64 caratteri)
API_KEY=8be20d…
FLASK_DEBUG=1
Ora python app.py parte. In app.py:
from dotenv import load_dotenv
load_dotenv() # legge .env e crea le "variabili d'ambiente"
app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY")
app.config["API_KEY"] = os.environ.get("API_KEY")
if not app.config["SECRET_KEY"] or not app.config["API_KEY"]:
raise RuntimeError("Mancano SECRET_KEY o API_KEY: copia .env.esempio in .env e completalo")
Le variabili d'ambiente sono impostazioni che il sistema operativo passa al programma. Sui server veri si impostano dal pannello di controllo, senza bisogno del file .env; la libreria python-dotenv serve a usarle comodamente anche in sviluppo.
Il raise RuntimeError è una scelta precisa: meglio non partire che partire con una chiave vuota o di prova.
Se la
SECRET_KEYcambia, tutti i cookie di sessione diventano non validi: gli utenti vengono scollegati. Se viene scoperta, chiunque può fabbricarsi un cookie e "diventare" qualsiasi utente.
2. .gitignore: cosa non va su GitHub
.env
.venv/
__pycache__/
*.db
Il file .gitignore dice a Git quali file ignorare. Il database resta fuori perché contiene i dati veri (e gli hash delle password); il .venv perché si ricrea con requirements.txt.
3. Le dipendenze con le versioni
flask==3.1.3
python-dotenv==1.2.4
waitress==3.0.2
requests==2.34.2
Con == e il numero di versione, chi installa il progetto (il server, un compagno, voi tra un anno) ottiene esattamente le stesse librerie. Per generare il file dalle librerie installate nel vostro ambiente virtuale: pip freeze > requirements.txt.
4. Un server vero: WSGI
app.run() avvia un server di sviluppo: gestisce male tante richieste insieme e non è pensato per la sicurezza. In produzione l'applicazione Flask viene "servita" da un server WSGI (Web Server Gateway Interface), lo standard con cui i server web parlano con le applicazioni Python.
browser ──HTTPS──► server web (Nginx / PythonAnywhere) ──► server WSGI ──► app Flask
(certificati, file statici) (Waitress, (il vostro
Gunicorn) codice)
Nel kit c'è avvia_server.py, con Waitress (funziona anche su Windows):
from waitress import serve
from app import app
serve(app, host="0.0.0.0", port=8080, threads=4)
python avvia_server.py # poi http://127.0.0.1:8080
Su Linux si usa spesso Gunicorn: gunicorn -w 4 app:app (4 processi, l'oggetto app dentro app.py).
5. Gli errori in produzione: la pagina 500
@app.errorhandler(500)
def errore_server(errore):
return render_template("500.html"), 500
Con il debug spento, se il codice solleva un'eccezione l'utente vede questa pagina gentile invece di un errore tecnico. Il dettaglio dell'errore finisce nel terminale (o nei log del server), dove lo legge solo chi gestisce il sito.
6. Cookie più sicuri
app.config["SESSION_COOKIE_HTTPONLY"] = True # JavaScript non può leggere il cookie
app.config["SESSION_COOKIE_SAMESITE"] = "Lax" # non viene mandato dai form di altri siti
Il primo limita i danni di un eventuale attacco XSS; il secondo blocca la maggior parte degli attacchi CSRF. Su un server con HTTPS si aggiunge SESSION_COOKIE_SECURE = True (il cookie viaggia solo cifrato).
7. Dove pubblicare
| Servizio | Pregi | Note |
|---|---|---|
| PythonAnywhere | gratuito per progetti piccoli, pensato per Python, HTTPS incluso | il più semplice per la scuola |
| Render, Railway | pubblicazione automatica da GitHub | piani gratuiti limitati |
| Un server Linux (il server del laboratorio, una VPS) | controllo totale | va configurato e mantenuto da voi |
| Un container Docker | lo stesso pacchetto gira ovunque | lo vediamo nei laboratori di quinta |
Su PythonAnywhere, passo passo
- Create un account gratuito (Beginner) su pythonanywhere.com.
- Scheda Files: caricate lo zip del progetto (senza
.env,.venvebacheca.db), poi da una console Bash estraetelo conunzip nomefile.zip. - Nella console Bash create l'ambiente virtuale e installate le librerie:
cd bacheca
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
- Create il file
.envdirettamente sul server (scheda Files → New file), con unaSECRET_KEYnuova eFLASK_DEBUG=0. - Scheda Web → Add a new web app → Manual configuration → Python 3.12.
- Nella stessa pagina: in Virtualenv indicate il percorso della cartella
.venv; poi aprite il WSGI configuration file e sostituite tutto con:
import sys
sys.path.insert(0, "/home/VOSTRONOME/bacheca")
from app import app as application
- Premete Reload. La bacheca è online su
https://VOSTRONOME.pythonanywhere.com. Se qualcosa non va, il link Error log nella scheda Web mostra l'errore.
Su PythonAnywhere non si usa né
app.run()né Waitress: il server WSGI è il loro, e cerca nel file di configurazione una variabile chiamataapplication.
La lista di controllo prima di pubblicare
- ☐
debugspento (FLASK_DEBUG=0) - ☐
SECRET_KEYlunga, casuale, letta dall'ambiente; mai la stessa dello sviluppo - ☐ nessuna password o chiave scritta nel codice
- ☐
.env,.venve database esclusi da Git - ☐
requirements.txtcon le versioni - ☐ pagine 404, 403 e 500 personalizzate
- ☐ tutte le route che modificano dati usano POST e sono protette (login + autorizzazione)
- ☐ tutte le query usano i segnaposto
? - ☐ password salvate con hash
- ☐ HTTPS attivo (PythonAnywhere e Render lo forniscono già)
Missioni
⭐ Missione 1 – Il file .env
Create il file .env come spiegato, avviate la bacheca con python app.py e poi con python avvia_server.py. Cosa cambia nel terminale? Poi mettete FLASK_DEBUG=0, provocate apposta un errore in una route (es. 1/0) e confrontate la pagina che vedete con debug acceso e spento.
⭐ Missione 2 – Caccia ai segreti
Cercate in tutto il progetto (in VS Code: Ctrl + Maiusc + F) le parole password, key, secret. Per ogni risultato, spiegate se è un problema o no e perché.
⭐⭐ Missione 3 – Online
Pubblicate la bacheca su PythonAnywhere seguendo i passi della lezione. Consegnate il link e una breve relazione: i passaggi seguiti, gli errori incontrati (con il messaggio dell'Error log) e come li avete risolti.
⭐⭐ Missione 4 – Il registro degli errori
Fate in modo che ogni errore 500 venga scritto, con data e ora, in un file errori.log, mentre l'utente continua a vedere la pagina gentile.
Aiutino
Nella funzione errore_server potete usare app.logger.error(...). Per scrivere su file, all'avvio: import logging e logging.basicConfig(filename="errori.log", level=logging.ERROR, format="%(asctime)s %(message)s").
⭐⭐⭐ Missione 5 – La revisione
Scambiatevi il progetto con un altro gruppo e controllate la loro applicazione con la lista di controllo della lezione. Scrivete un breve rapporto: per ogni punto, ok oppure il problema trovato (con il file e la riga) e come risolverlo.