Spiegazione delle variabili d’ambiente: file .env, segreti e configurazione eseguiti correttamente

  1. Home
  2. Blog
  3. DevOps

DevOps

Spiegazione delle variabili d’ambiente: .env file, segreti e configurazione eseguiti correttamente

Le variabili di ambiente consentono l’esecuzione dello stesso codice su un laptop, in CI e in produzione con impostazioni diverse. Ecco come si comportano realmente, con le sorprese che ci sono capitate provandole.

8 min di lettura
Spiegazione delle variabili d'ambiente: file .env, segreti e configurazione eseguiti correttamente

Punti chiave

  • Le variabili di ambiente sono impostazioni di valori-chiave che un processo riceve dal suo ambiente.
  • Mantengono la configurazione, come porte e URL di database, fuori dal tuo codice.
  • I valori sono sempre stringhe, quindi “falso” è vero a meno che non lo analizzi.
  • Non eseguire mai il commit di .env file con segreti reali e convalidare le impostazioni richieste all’avvio dell’app.

La tua app necessita di un URL di database. Sul tuo laptop è un database locale, in CI uno usa e getta e in produzione un cluster gestito. L’hardcoding di ognuno di essi significa modificare il codice per ogni ambiente. Le variabili ambientali risolvono questo problema. Il codice recita DATABASE_URL e ogni ambiente fornisce il proprio valore.

Questa idea è fondamentale per il metodo dell’app Twelve-Factor, secondo il quale la configurazione che varia tra le distribuzioni appartiene all’ambiente. Abbiamo testato il comportamento seguente con Python 3.13 su Windows. Ogni output mostrato proviene da quella corsa.

Cosa sono le variabili d’ambiente?

Ogni processo in esecuzione ha una serie di valori di testo denominati chiamati il ​​suo ambiente. Esempi familiari sono PATH, che dice alla shell dove trovare i programmi, e HOME o USERPROFILE. La tua app può utilizzare lo stesso meccanismo per impostazioni come APP_PORT, LOG_LEVEL o DATABASE_URL.

Impostazione delle variabili d’ambiente

Conchiglia Per questa sessione Per un comando
Bash/zsh esporta APP_PORT=8080 APP_PORT=8080 python app.py
PowerShell $env:APP_PORT = "8080" Impostalo, eseguilo, quindi rimuovilo
Windows cmd imposta APP_PORT=8080 Non integrato

Questi durano solo per la sessione terminale corrente. Per renderli permanenti, aggiungili a un profilo shell come ~/.bashrc o, su Windows, utilizza le impostazioni dell’ambiente di sistema o setx. Per le applicazioni, in genere è meglio fornirle al momento della distribuzione anziché impostarle a livello di sistema.

Leggendoli in codice

import os

port = os.environ.get("APP_PORT", "8000")   # default if missing
print(port)                                  # 8000

db = os.environ["DATABASE_URL_MISSING_DEMO"]
# KeyError: 'DATABASE_URL_MISSING_DEMO'

Utilizzare .get() con un valore predefinito per le impostazioni opzionali. Utilizza le parentesi quadre, che sollevano KeyError, per le impostazioni senza le quali l’app non può funzionare. Altri linguaggi sono simili: process.env.APP_PORT in Node.js, System.getenv("APP_PORT") in Java e os.Getenv("APP_PORT") in Go.

Tutto è una corda

Questo cattura persone in ogni lingua. Impostiamo una porta e poi aggiungiamo “1” ad essa:

os.environ["APP_PORT"] = "8080"
port = os.environ["APP_PORT"]
print(type(port).__name__, repr(port), port + "1")
# str '8080' 80801

os.environ["APP_PORT"] = 8080
# TypeError: str expected, not int

Il valore è tornato come testo e l’aggiunta ha prodotto 80801, non 8081. Convertire esplicitamente, come in int(os.environ["APP_PORT"]). I booleani sono peggio:

os.environ["DEBUG"] = "false"
print(bool(os.environ["DEBUG"]))   # True

Qualsiasi stringa non vuota è vera in Python, quindi "false" è diventato True. La modalità debug sarebbe stata attivata in produzione. Analizza deliberatamente i booleani:

def env_bool(name, default=False):
    value = os.environ.get(name)
    if value is None:
        return default
    return value.strip().lower() in {"1", "true", "yes", "on"}

env_bool("DEBUG")                   # False
env_bool("NOT_SET", default=True)   # True

Come i processi li ereditano

Un processo figlio riceve una copia dell’ambiente genitore. Abbiamo avviato i processi Python figlio per vedere questo:

child sees: 8080 inherited     # default: full copy of the parent's environment
child sees: 9000 None          # given a custom env: only what we passed
parent sees child var: None    # a child's changes never flow back

Seguono tre lezioni pratiche. Le variabili che esporti sono visibili a ogni programma che avvii, inclusi gli script che non hai scritto, il che è importante per i segreti. Passare un ambiente personalizzato sostituisce il tutto, quindi includi tutto ciò di cui il bambino ha bisogno. E uno script non può modificare le variabili della shell genitore, motivo per cui gli strumenti ti chiedono di source un file invece di eseguirlo.

file .env

Digitare molte variabili a mano diventa noioso, quindi la maggior parte dei progetti mantiene i valori di sviluppo locale in un file .env:

# local development only
APP_PORT=8080
DATABASE_URL="postgres://dev:devonly@localhost:5432/shop"
FEATURE_SEARCH=true

Librerie come python-dotenv e dotenv per Node.js caricano questo file all’avvio. Node.js 20.6 e versioni successive possono anche caricarne uno in modo nativo con --env-file. Per rendere l’idea, abbiamo scritto un piccolo parser, che ha trasformato il file sopra in:

{'APP_PORT': '8080',
 'DATABASE_URL': 'postgres://dev:devonly@localhost:5432/shop',
 'FEATURE_SEARCH': 'true'}

Utilizza una vera libreria nella pratica. Gestisce virgolette, escape, commenti e valori su più righe che la nostra versione a 10 righe ignora. Per impostazione predefinita, la maggior parte dei caricatori dotenv non sovrascrive le variabili già impostate, quindi i valori dell’ambiente reale prevalgono sul file. Di solito è quello che vuoi.

Suggerimento: Aggiungi .env a .gitignore prima del tuo primo commit e commit invece di un .env.example con ogni nome di variabile e valori segnaposto sicuri. I nuovi sviluppatori lo copiano e inseriscono i propri valori.

Convalidare la configurazione all’avvio

Una variabile mancante dovrebbe arrestare immediatamente l’app con un messaggio chiaro, non causare un errore confuso un’ora dopo. Abbiamo controllato le impostazioni richieste durante il caricamento:

REQUIRED = ["DATABASE_URL", "SECRET_KEY"]

def check_required(env):
    missing = [k for k in REQUIRED if not env.get(k)]
    if missing:
        raise SystemExit(f"Missing required settings: {', '.join(missing)}")

# Missing required settings: SECRET_KEY

Le librerie di configurazione tipizzate vanno oltre. Le impostazioni Pydantic in Python, zod o envalid in Node.js e le proprietà di configurazione di Spring in Java convertono tutti i tipi e segnalano ogni problema contemporaneamente.

Variabili d’ambiente e segreti

Le variabili di ambiente rappresentano il modo comune per passare segreti come le chiavi API a un’app e sono molto meglio che codificarle in modo rigido. Hanno dei punti deboli da conoscere:

  • I processi figli li ereditano, come ha dimostrato il nostro test.
  • Possono apparire nei rapporti sugli arresti anomali, nelle pagine di debug e nei registri che scaricano l’ambiente.
  • Su alcuni sistemi, altri processi eseguiti dallo stesso utente possono leggerli.

Per la produzione, molti team mantengono i segreti in un gestore dedicato, come HashiCorp Vault, AWS Secrets Manager, Azure Key Vault o Google Secret Manager, e li inseriscono in fase di runtime. Alcuni preferiscono montare i segreti come file anziché come variabili. In ogni caso, non stampare mai la configurazione nei log e ruotare qualsiasi segreto che sia mai stato impegnato in Git, anche dopo averlo rimosso dalla cronologia.

In Docker e CI

# Docker (not run for this article)
docker run -e APP_PORT=8080 --env-file .env.production myapp

# GitHub Actions (not run for this article)
env:
  APP_PORT: 8080
  API_KEY: ${{ secrets.API_KEY }}

Le piattaforme CI archiviano i segreti crittografati e li mascherano nei log. Evitare di inserire segreti nelle immagini Docker con ENV nel Dockerfile, perché rimangono leggibili nei livelli dell’immagine. Consulta il nostro tutorial su Docker Compose e la guida CI/CD di GitHub Actions per le configurazioni complete.

Migliori pratiche

  • Utilizzare nomi chiari e maiuscoli con un prefisso, ad esempio SHOP_DB_URL.
  • Leggi tutta la configurazione in un modulo, converti i tipi lì e convalida all’avvio.
  • Fornire impostazioni predefinite sicure solo per le impostazioni non sensibili.
  • Mantieni .env fuori da Git e mantieni un .env.example.
  • Utilizzare un gestore dei segreti in produzione per i valori sensibili.
  • Documenta ogni variabile, il suo tipo e un valore di esempio.

Debug dei problemi relativi alle variabili di ambiente

Quando un’app si comporta come se mancasse un’impostazione, esegui questi controlli in ordine:

  1. È impostata nel posto giusto? Una variabile esportata in una scheda del terminale non esiste in un’altra o in un servizio avviato dal sistema.
  2. L’app è stata riavviata? Un processo in esecuzione mantiene l’ambiente con cui è stato avviato. La modifica successiva di una variabile non ha alcun effetto fino al riavvio.
  3. Il nome è esatto? I nomi fanno distinzione tra maiuscole e minuscole su Linux e macOS, quindi App_Port e APP_PORT sono variabili diverse. Windows li tratta allo stesso modo.
  4. Un file .env lo sovrascrive o viene ignorato? Controlla se il tuo caricatore sovrascrive i valori esistenti e quale file legge effettivamente.
  5. Sono presenti virgolette o spazi? Un valore copiato con uno spazio finale o virgolette extra può non essere analizzato o non trovare una corrispondenza in modi difficili da individuare.

Per vedere cosa ha realmente ricevuto un processo, stampa i nomi delle variabili, mai i valori dei segreti. In Linux e macOS, printenv elenca l’ambiente della shell corrente e in PowerShell Get-ChildItem Env: fa lo stesso. All’interno di un contenitore, l’esecuzione da env a docker exec mostra la vista del contenitore, che spesso è diversa da quella della propria shell.

Un ultimo consiglio: registra una riga all’avvio che elenca quali impostazioni opzionali utilizzano i valori predefiniti. Non costa nulla e la maggior parte delle persone risponde “perché lo fa?” domande subito.

Considerazioni finali

Le variabili d’ambiente sono semplici, ma nascondono alcune trappole: tutto è una stringa, i bambini le ereditano e i segreti possono trapelare attraverso di esse. Trattali come input per la tua app. Leggili in un unico posto, convertili e convalidali in anticipo e mantieni i veri segreti lontani dai file che vengono sottoposti a commit.

Domande frequenti

Cos’è una variabile d’ambiente?

È un valore di testo con nome che un processo riceve dal suo ambiente, ad esempio APP_PORT=8080. Le app li utilizzano per le impostazioni che cambiano da un ambiente all’altro.

Cos’è un file .env?

Un file di testo di righe KEY=value, caricato all’avvio da una libreria come python-dotenv. Viene utilizzato principalmente per lo sviluppo locale e non dovrebbe essere impegnato con veri segreti.

Perché la mia variabile d’ambiente è una stringa?

I sistemi operativi memorizzano i valori dell’ambiente come testo. Converti i numeri con int() e analizza esplicitamente i booleani, perché la stringa “false” conta come vera.

Le variabili di ambiente sono sicure per i segreti?

Sono migliori dell’hardcoding, ma i processi secondari li ereditano e i log possono esporli. I sistemi di produzione utilizzano spesso un gestore dei segreti dedicato.

Uno script può modificare le variabili della shell madre?

No. Un processo figlio ottiene una copia dell’ambiente, quindi le sue modifiche non ritornano. Per impostare variabili nella shell corrente, invece, procurati il ​​file.

Continua a imparare

Lavora con Stack Quora

Hai bisogno di aiuto con test, WordPress o SEO?

Aiutiamo i team a fornire software migliore e a far crescere i loro siti. Raccontaci a cosa stai lavorando e ti suggeriremo chiaramente il prossimo passo.

Continua a leggere

Altre guide su test, intelligenza artificiale e sviluppo moderno

Ti è piaciuto questo articolo? Scegli un argomento e passa direttamente ai nostri tutorial più recenti.

Intelligenza artificiale

Spiegazione delle allucinazioni dell'intelligenza artificiale: perché i modelli linguistici inventano le cose e come ridurle

Spiegazione delle allucinazioni dell'intelligenza artificiale: cosa sono, perché i modelli linguistici le producono, tipi comuni, come ridurle con b…

Leggi l'articolo →
DevOps

Spiegazione delle variabili d'ambiente: file .env, segreti e configurazione eseguiti correttamente

Variabili d'ambiente spiegate con esempi Python che abbiamo eseguito: lettura e impostazione, perché ogni valore è una stringa, file .env, ereditari…

Leggi l'articolo →
Sviluppo software

Spiegazione della limitazione della velocità API: algoritmi, intestazioni e gestione degli errori 429

La limitazione della velocità dell'API spiegata con una simulazione che abbiamo eseguito: bucket di token, finestra fissa, finestra scorrevole, risp…

Leggi l'articolo →
Automazione dei test

Derisione in Python: unittest.mock, patch e gli errori che fanno mentire i test

Mocking in Python con unittest.mock, testato: Mock e return_value, side_effect, patch dove viene utilizzato un nome, specifiche e autospec, date moc…

Leggi l'articolo →
Test del software

Spiegazione della copertura del codice: copertura di linea o di filiale e cosa significa realmente il 100%.

Copertura del codice spiegata con una vera esecuzione di cover.py: copertura di linea, ramo e condizione, lettura di un rapporto, perché la copertur…

Leggi l'articolo →
Database e SQL

DELETE vs TRUNCATE vs DROP in SQL: differenze, rollback e quando utilizzarli

DELETE vs TRUNCATE vs DROP spiegato con SQL testato: cosa rimuove, rollback, trigger, reimpostazione dell'identità, velocità e come differiscono Pos…

Leggi l'articolo →
Database e SQL

Spiegazione delle visualizzazioni SQL: crea, utilizza ed evita le insidie, con esempi

Viste SQL spiegate con le query che abbiamo eseguito: creazione di una vista, risultati in tempo reale, nascondere colonne, prestazioni, viste aggio…

Leggi l'articolo →
Sviluppo software

Interfaccia TypeScript e tipo: differenze reali, testate con il compilatore

Interfaccia TypeScript vs tipo, verificata con il vero compilatore: cosa possono fare entrambi, cosa può fare solo uno, fusione delle dichiarazioni,…

Leggi l'articolo →
Sviluppo software

Spiegazione di Python *args e **kwargs: funzioni flessibili con esempi chiari

Python *args e **kwargs spiegati con il codice che abbiamo eseguito: compressione e decompressione, ordine dei parametri, argomenti solo per parole …

Leggi l'articolo →
Sviluppo software

Funzioni Lambda Python: sintassi, usi reali e quando non usarli

Funzioni lambda Python spiegate con il codice che abbiamo eseguito: sintassi, ordinamento con chiave, mappa e filtro, chiusure, trap dell'associazio…

Leggi l'articolo →
Intelligenza artificiale

Cosa sono gli agenti AI? Come funzionano, con un semplice ciclo di agenti che puoi eseguire

Cosa sono gli agenti IA? Una semplice guida al ciclo dell'agente, agli strumenti, alla memoria e ai guardrail, con un piccolo ciclo Python che abbia…

Leggi l'articolo →
DevOps

Monitoraggio vs osservabilità: la vera differenza, con log, metriche e tracce

Spiegazione del monitoraggio e dell'osservabilità: errori noti e sconosciuti, log, metriche e tracce, una procedura dettagliata sugli incidenti di p…

Leggi l'articolo →
DevOps

Volumi Docker e montaggi di associazione: mantenere i dati del contenitore nel modo giusto

Volumi Docker e montaggi di collegamento spiegati: perché i dati del contenitore scompaiono, volumi denominati, montaggi di collegamento, tmpfs, ese…

Leggi l'articolo →
Automazione dei test

TDD vs BDD: differenze chiave, una caratteristica costruita in entrambi i modi e quando utilizzarli ciascuno

TDD vs BDD spiegato con una funzione di sconto costruita in entrambi i modi: refactoring rosso-verde con il codice che abbiamo eseguito, scenari "Da…

Leggi l'articolo →
Sviluppo software

Git merge vs rebase: differenze, esempi e quando usare ciascuno

Git merge vs rebase spiegato con output reale del log, una tabella di confronto, la regola d'oro del rebase e un flusso di lavoro sicuro per i team.

Leggi l'articolo →
Sviluppo software

List comprehension in Python: sintassi, esempi e quando evitarla

List comprehension in Python spiegata: sintassi, filtri, if-else, cicli annidati, tempi misurati e i casi in cui un normale ciclo for è meglio.

Leggi l'articolo →
Database e SQL

SQL JOIN spiegati con esempi (Inner, Left, Right, Full)

Capisci i JOIN SQL passo dopo passo con tabelle e query di esempio: INNER, LEFT, RIGHT, FULL, CROSS e SELF, con errori comuni e consigli sulle prest…

Leggi l'articolo →