Verschil tussen .env, env_file en environment in Docker
Begrijp het verschil tussen .env, env_file en environment in Docker Compose. Leer de voorrangsvolgorde en ontdek waarom wachtwoorden in Docker Secrets thuishoren.
De drie betekenissen van een env-bestand
Docker Compose kent drie afzonderlijke mechanismen met verwarrend op elkaar lijkende namen. Het .env-bestand vult ${VARIABLE}-placeholders in compose.yaml zelf in, nog voordat Compose het bestand parseert. Het env_file:-attribuut laadt een bestand met sleutel/waarde-paren in de omgeving van de container. Het environment:-attribuut stelt variabelen direct in op de container, geschreven in het compose-bestand. Ze zijn niet uitwisselbaar, en wanneer twee ervan dezelfde sleutel instellen, wordt de winnaar bepaald door een gedocumenteerde voorrangsvolgorde.
Deze handleiding toont de werking van elk mechanisme, bewijst de voorrangsvolgorde met een commando dat u kunt uitvoeren, en behandelt vervolgens het belangrijkste aspect: omgevingsvariabelen zijn leesbaar voor iedereen die docker inspect kan uitvoeren, dus wachtwoorden horen hier niet in thuis. Als u nieuw bent met compose-bestanden in het algemeen, begin dan bij Docker Compose basisbeginselen op een VPS en keer daarna hier terug voor de configuratie.
Het .env-bestand is voor het compose-bestand, niet voor de container
Maak een map aan en plaats daar twee bestanden in.
mkdir -p ~/envdemo && cd ~/envdemo
printf 'ALPINE_TAG=3.20\n' > .envservices:
demo:
image: alpine:${ALPINE_TAG}
command: printenv ALPINE_TAGVraag nu aan Compose wat er daadwerkelijk is geparseerd.
docker compose configDe output toont image: alpine:3.20. De placeholder is verdwenen, omdat de interpolatie plaatsvond tijdens het parsen. Compose zoekt naar .env in de projectmap, de map die het compose-bestand bevat, en vervangt elke ${NAME} die het tegenkomt.
Start vervolgens de service.
docker compose run --rm demoprintenv ALPINE_TAG stopt met status 1 en geeft geen output. De variabele bestaat niet binnen de container. Dit is het meest voorkomende misverstand: .env configureerde het compose-bestand, niet het proces. Een .env-bestand met POSTGRES_PASSWORD=hunter2 erin doet helemaal niets voor uw database, tenzij een onderdeel van het compose-bestand ernaar verwijst.
${NAME:-default} biedt een fallback wanneer de variabele niet is ingesteld of leeg is. ${NAME:?message} zorgt ervoor dat Compose weigert te starten en uw bericht toont; dit is de juiste keuze voor een waarde waarvoor geen veilige standaardwaarde bestaat.
env_file laadt variabelen in de container
Het env_file:-attribuut benoemt een of meer bestanden waarvan de inhoud omgevingsvariabelen van de container wordt.
printf 'GREETING=from_env_file\nAPP_MODE=production\n' > app.envservices:
demo:
image: alpine:3.20
command: printenv GREETING
env_file:
- ./app.envdocker compose run --rm demoDit print from_env_file. Het bestandsformaat bestaat uit platte KEY=value-regels, één per regel, waarbij # een commentaar start. Het is geen shell. Aanhalingstekens worden in de meeste gevallen als onderdeel van de waarde behouden en export-prefixen zijn niet nodig. Plaats geen spaties rond het =-teken, omdat KEY = value een variabele produceert die letterlijk KEY heet, met een spatie aan het begin van de waarde.
Een ontbrekend env_file-pad is een fout en Compose stopt. Markeer het als optioneel als het bestand legitiem afwezig mag zijn:
env_file:
- path: ./app.env
required: falseenvironment stelt variabelen inline in
services:
demo:
image: alpine:3.20
command: printenv GREETING
environment:
GREETING: from_environmentEr worden twee syntactische vormen geaccepteerd: de bovenstaande mapping-vorm en een lijstvorm met - GREETING=from_environment. Deze gedragen zich identiek. De lijstvorm heeft één extra functie: een losse sleutel zonder waarde geeft de variabele door vanuit de shell waar u docker compose heeft uitgevoerd.
environment:
- GREETINGGREETING=from_my_shell docker compose run --rm demoDit voert from_my_shell uit. Voer het uit zonder GREETING in de shell in te stellen en Compose stelt niets in, zonder waarschuwing. Het is belangrijk om te weten dat doorgeef-fouten stil blijven, omdat een service die start met een lege wachtwoordvariabele vaak succesvol opstart en simpelweg volledig openstaat.
Welke instelling krijgt voorrang
Docker documenteert de voorrangsvolgorde, van hoog naar laag: docker compose run -e op de opdrachtregel, gevolgd door environment of env_file waarvan de waarde wordt geïnterpoleerd vanuit uw shell of een env-bestand, daarna de gewone environment in het compose-bestand, vervolgens env_file, en tot slot de ENV-richtlijn die in de image is ingebakken.
De korte versie voor dagelijks gebruik: environment: wint van env_file:, en -e op de opdrachtregel wint van beide. U kunt dit aantonen in één bestand.
services:
demo:
image: alpine:3.20
command: printenv GREETING
env_file:
- ./app.env
environment:
GREETING: from_environmentdocker compose run --rm demo
docker compose run --rm -e GREETING=from_cli demo printenv GREETINGDe eerste opdracht print from_environment, dus environment: overschreef de waarde in app.env. De tweede opdracht print from_cli. Niets in het compose-bestand overschrijft de opdrachtregel.
Wanneer een container zich gedraagt alsof uw configuratie niet is toegepast, ga dan niet gissen. docker compose config print het volledig opgeloste bestand en docker compose config --environment print de interpolatievariabelen waarmee Compose werkt. De meeste meldingen van "mijn env-bestand wordt genegeerd" blijken te berusten op een waarde die op twee verschillende niveaus is ingesteld.
Waarom omgevingsvariabelen lekken
Stel een wachtwoord in via environment: en het wordt opgeslagen in de configuratie van de container op schijf, zichtbaar voor elke gebruiker in de docker-groep.
docker compose run -d --name leaky -e DB_PASSWORD=hunter2 demo sleep 300
docker inspect leaky --format '{{json .Config.Env}}'De uitvoer bevat "DB_PASSWORD=hunter2" in leesbare tekst. Drie andere paden leggen dezelfde waarde bloot. docker compose config print het naar de terminal, wat de reden is dat het vaak in een ondersteuningsforum wordt geplakt. Elk proces binnen de container kan /proc/1/environ lezen en elk onderliggend proces erft de variabele. Bovendien dumpen crash-handlers van applicaties routinematig de volledige omgeving in een logbestand of foutrapport.
Lidmaatschap van de docker-groep staat effectief gelijk aan root-toegang op de host; dit is dus geen privilegegrens waar u op kunt vertrouwen. De handleiding over gebruikersaccounts met minimale rechten op een VPS legt uit waarom het beperken van die groep op elke gedeelde server noodzakelijk is.
Compose secrets bewaren de waarde in een bestand
Compose ondersteunt secrets op basis van bestanden. De waarde wordt als bestand in de container gemount in plaats van in de omgeving geïnjecteerd.
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./db_password.txtHet secret wordt gemount op /run/secrets/db_password binnen de container. De naam na de slash is de naam van het secret uit het secrets:-blok op het hoogste niveau.
Het _FILE-achtervoegsel is een conventie die wordt gebruikt door de officiële Docker-images, waaronder postgres, mysql en mariadb. Deze entrypoint-scripts controleren op VARNAME_FILE, lezen het bestand en gebruiken de inhoud ervan. Dit is geen Docker-functionaliteit, dus het werkt alleen waar de image dit implementeert. Controleer de documentatie van de image voordat u ervan uitgaat dat SOMETHING_FILE wordt ondersteund. Applicaties die dit niet ondersteunen, kunnen het bestand vaak zelf bij het opstarten lezen, of u kunt het pad doorgeven en uw eigen entrypoint het werk laten doen.
Controleer dit vanuit de draaiende container:
docker compose exec db cat /run/secrets/db_password
docker compose exec db printenv POSTGRES_PASSWORDHet eerste commando toont het wachtwoord. Het tweede toont niets, omdat de waarde nooit in de omgeving is terechtgekomen. Dat is precies het doel: docker inspect op deze container toont alleen het ongevaarlijke pad.
Beveilig het bronbestand op de host, want het secret is slechts zo privé als het bestand erachter:
chmod 600 db_password.txtDe pragmatische middenweg op een VPS
Veel self-hosted images ondersteunen geen _FILE-variabelen, waardoor omgevingsvariabelen de enige toegangsweg zijn. Op een VPS met één beheerder is het realistische doel om te voorkomen dat de waarden in een voor iedereen leesbaar bestand in uw projectmap staan, en om ze buiten git te houden.
sudo install -o root -g root -m 600 /dev/null /etc/myapp/app.env
sudo nano /etc/myapp/app.env env_file:
- /etc/myapp/app.envinstall -m 600 maakt het bestand aan met de rechten al ingesteld, zodat er geen moment is waarop het voor iedereen leesbaar is. Root is de eigenaar, dus een niet-rootgebruiker op de server kan het niet lezen, hoewel iedereen die docker kan uitvoeren de waarde nog steeds uit de container kan uitlezen. Voeg *.env en .env toe aan .gitignore en commit een app.env.example die de sleutelnamen met lege waarden bevat. Een gecommitteerd wachtwoord is een gecompromitteerd wachtwoord dat moet worden geroteerd.
Het roteren van een waarde betekent dat de service opnieuw moet worden opgestart. Omgevingsvariabelen worden eenmalig gelezen wanneer het containerproces start; het bewerken van het bestand verandert dus niets totdat u docker compose up -d --force-recreate db uitvoert. Dit is hetzelfde patroon als gebruikt in de n8n achter HTTPS op een VPS-handleiding, waarbij de encryptiesleutel buiten het compose-bestand wordt bewaard.
Configuratie opsplitsen per omgeving
Compose leest .env standaard vanuit de projectmap. Wijs naar een andere locatie met --env-file.
docker compose --env-file .env.staging configMeerdere bestanden worden in volgorde gelezen, waarbij latere bestanden de eerdere overschrijven. Houd de niet-geheime standaardinstellingen in een gecommit bestand en de geheimen in een bestand dat de server nooit verlaat. Hetzelfde geldt voor env_file:, waarbij het laatst vermelde bestand wint bij een dubbele sleutel.
FAQ
Waarom wordt mijn .env-bestand genegeerd in de container?
Het wordt niet genegeerd. Het .env-bestand vervangt alleen ${NAME}-placeholders in het compose-bestand. Het stelt nooit variabelen in binnen een container. Om de waarde in de container te krijgen, moet u ernaar verwijzen: environment: { KEY: "${NAME}" }, of gebruik in plaats daarvan env_file: ./that-file.env.
Overschrijft environment de env_file, of andersom?
environment: wint. De gedocumenteerde volgorde van Docker plaatst het environment-attribuut boven het env_file-attribuut, en beide staan onder docker compose run -e op de opdrachtregel. Als een sleutel op beide plekken is ingesteld, wordt de waarde in env_file stilzwijgend genegeerd.
Hoe zie ik de uiteindelijke waarde die Compose gebruikt?
Voer docker compose config uit om het volledig opgeloste compose-bestand met alle toegepaste interpolatie af te drukken. Voor een container die al draait, laat docker inspect <container> --format '{{json .Config.Env}}' precies zien wat het proces heeft ontvangen.
Zijn Compose-geheimen versleuteld?
Nee. Een op bestanden gebaseerd geheim wordt als een plat tekstbestand in de container gemount op /run/secrets/<name>, en het bronbestand staat onversleuteld op de schijf van de host. Het voordeel is de scope, niet de versleuteling: de waarde blijft buiten de containeromgeving, buiten de docker inspect-uitvoer en buiten crashdumps die de omgeving afdrukken.
Kan ik aanhalingstekens en spaties gebruiken in een env-bestand?
Gebruik KEY=value with spaces en laat de aanhalingstekens weg. Compose behandelt de rest van de regel als de waarde, waardoor aanhalingstekens meestal als letterlijke tekens in de waarde terechtkomen. Plaats nooit spaties rond de =, omdat de sleutel dan een afsluitende spatie bevat en niets ermee overeenkomt.