fbpx

Przeglądając grupy tematyczne związane z Dockerem, zauważyłem, że wiele osób ma problem z zrozumieniem, w jaki sposób można przekazywać zmienne środowiskowe do obrazu i do kontenera. Zmotywowało mnie to do stworzenia tego artykułu.

Z artykułu dowiesz się WSZYSTKO na temat zmiennych środowiskowych w Dockerze. Oczywiście wszystko na przykładach, tak by łatwiej było zrozumieć, a następnie móc zastosować u siebie.

Post ten jest kontynuacją serii, w której opisują poszczególne instrukcje Dockerfile. Jeżeli jeszcze nie widziałeś, koniecznie sprawdź COPY vs ADD oraz CMD vs ENTRYPOINT.


Zastosowania instrukcji ENV

Wewnątrz pliku Dockerfile, instrukcja ENV służy do tworzenia zmiennych środowiskowych. Podczas uruchamiania kontenera na podstawie obrazu, możemy nadpisać ich wartość. Taka praktyka jest bardzo często stosowana.

Do czego mogą służyć zmienne środowiskowe? Niemal każda aplikacja ma jakąś konfigurację.

Przykłady:

  • adres IP do bazy danych
  • adres IP do innych usług (Elasticsearch, Vault etc.)
  • zmienne związane z technologią (NODE_ENV, JAVA_VERSION etc)
  • zmienne związane z logiką biznesową


Sposoby na przekazywanie zmiennych środowiskowych

1. Uruchamianie kontenera z terminala

Jeżeli tworzysz kontener korzystając z terminala, do nadpisania lub ustawienia zmiennej środowiskowej służy argument -e KLUCZ=WARTOŚĆ. Poniżej tworzymy kontener WordPressa, przekazując w zmiennych środowiskowych połączenie do bazy danych.

docker run -e WORDPRESS_DB_HOST=10.1.2.3:3306 \
    -e WORDPRESS_DB_USER=user \
    -e WORDPRESS_DB_PASSWORD=somepassword \
    -d wordpress

2. Uruchamianie z pomocą docker-compose

Ten sam efekt możesz uzyskać wewnątrz pliku docker-compose.yml

version: '3.1'

services:

  wordpress:
    image: wordpress
    environment:
       - WORDPRESS_DB_HOST: 10.1.2.3:3306
       - WORDPRESS_DB_USER: user 
       - WORDPRESS_DB_PASSWORD: somepassword
       - WORDPRESS_DB_NAME: exampledb

3. Bezpośrednio w Dockerfile

Istnieją pewne przypadki, dla których na stałe chcemy ustawić zmienną środowiskową. Jak już wyżej wspominałem, może dotyczyć to technologii, w której tworzymy aplikację. Przykładowo, chcemy na stałe ustawić zmienną środowiskową NODE_VERSION. Przykład poniżej.

FROM scratch

# set up node
ENV NODE_VERSION 8.9.4
ENV NODE_DOWNLOAD_SHA 21fb4690e349f82d708ae766def01d7fec1b085ce1f5ab30d9bda8ee126ca8fc
RUN curl -SL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz" --output nodejs.tar.gz \
    && echo "$NODE_DOWNLOAD_SHA nodejs.tar.gz" | sha256sum -c - \
    && tar -xzf "nodejs.tar.gz" -C /usr/local --strip-components=1 \
    && rm nodejs.tar.gz \
    && ln -s /usr/local/bin/node /usr/local//nodejs


Stosowanie plików .env

Istnieje możliwość stworzenia pliku o nazwie .env. Zawartość tego pliku może posłużyć do nadpisania wartości znajdujących się w pliku docker-compose.yml. Warto zaznaczyć, że oba pliki muszą znajdować się w tym samym katalogu.

Zawartość pliku .env defniujemy za pomocą schematu klucz=wartość

DB_PORT=5434
DB_USER=myuser

Przykład pliku docker-compose, gdzie to zadziała. Zwróć uwagę w jaki sposób określamy, że zmienna ma zostać podstawiona. Jest to składnia ${DB_PORT} oraz ${DB_USER}

version: '3.2'

services:
  postgres:
    image: postgres:9.6
    ports:
      - ${DB_PORT}:5432
    environment:
        POSTGRES_PASSWORD: secretpassword
        POSTGRES_USER: ${DB_USER}

Aby upewnić się, że wartości są podstawiane prawidłowo wystarczy użyć komendy docker-compose config

$ docker-compose config                  
services:                                
  postgres:                              
    environment:                         
      POSTGRES_PASSWORD: secretpassword  
      POSTGRES_USER: myuser              
    image: postgres:9.6                  
    ports:                               
    - published: 5434                    
      target: 5432                       
version: '3.2'

W miejsce ${DB_USER} została podstawiona wartość myuser, a w miejsce ${DB_PORT} wartość 5434. Wartości te pochodzą z pliku .env. Warto podkreślić, że oba pliki muszą znajdować się w tym samym katalogu.

Gdzie takie podejście może mieć zastosowanie?

Plik docker-compose.yml jest dodawany do kontroli wersji, natomiast plik .env już nie. Dzięki temu, każdy z członków zespołu może mieć inną zawartość pliku .env według własnych potrzeb, co nie będzie powodować żadnych zmian w repozytorium.


Zmienne środowiskowe na hoście nadpisują wpisy w pliku .env

Jeżeli na hoście istnieje zmienna o takiej samej nazwie jak wewnątrz pliku .env, wartość zostanie pobrana z zmiennej środowiskowej hosta! Sprawdźmy to na przykładzie. Na hoście ustawiłem wartość zmiennej środowiskowej JAVA_HOME na C:\Program Files\Java\jdk1.8.0_221

Mamy następujące plik docker-compose.yml, w którym odwołujemy się do ${JAVA_HOME}

version: '3.2'

services:
  myapp:
    image: openjdk:7
    environment:
        JAVA_DIRECTORY: ${JAVA_HOME}

W tym samym katalogu znajduje się również plik .env zawierający zmienną JAVA_HOME=D:/java

JAVA_HOME=D:/java

Teraz pozostaje sprawdzić całość za pomocą polecenia docker-compose config

$ docker-compose config
services:
  myapp:
    environment:
      JAVA_DIRECTORY: C:\Program Files\Java\jdk1.8.0_221
    image: openjdk:7
version: '3.2'

Rezulat? Zamiast wartości zdefiniowanej w pliku .env, została podstawiona wartość zmiennej środowiskowej z hosta! (C:\Program Files\Java\jdk1.8.0_221)


Zmienne w terminalu nadpisują wszystko

Nie można było o tym nie wspomnieć. Definiując zmienną środowiskową wewnątrz terminala, ma ona najwyższy priorytet.

Ustawiamy zmienną środowiskową (system Windows):

$ set JAVA_HOME=D:\shell_variable_path

Obecny stan zmiennych o nazwie JAVA_HOME:

  • na hoście: C:\Program Files\Java\jdk1.8.0_221
  • w pliku .env:  D:\java
  • w terminalu: D:\shell_variable_path

Tradycyjnie sprawdzamy rezultat poleceniem docker-compose config

$ docker-compose config
services:
  myapp:
    environment:
      JAVA_DIRECTORY: D:\shell_variable_path
    image: openjdk:7
version: '3.2'

Tym sposobem mamy potwierdzenie, iż zmienne środowiskowe zdefiniowane w terminalu mają najwyższy priorytet!

UWAGA: Plik .env nie ma nic wspólnego z instrukcjami ENV wewnątrz Dockerfile oraz argumentem `-e` w terminalu!  Oznacza to, że wartości zdefiniowane w pliku .env, NIE trafią bezpośrednio do kontenera, a służą tylko do podstawiania wartości w pliku docker-compose.yml


Inne sposoby na przekazywanie zmiennych środowiskowych do kontenera

Po zbudowaniu obrazu, możemy uruchamiać kontenery i przekazywać do nich zmienne środowiskowe na kilka sposobów. W każdym przypadku, spowoduje to nadpisanie domyślnych wartości zdefiniowanych w Dockerfile.

1. Zmienne środowiskowe z hosta

Jak to działa? Standardowo korzystamy z argumentu -e, ale z małą różnicą. Podajemy tylko nazwę zmiennej, bez wartości. W ten sposób docker pobiera zmienną środowiskową zdefiniowaną w terminalu (jeżeli istnieje) lub w systemie hosta.

Obecnie w terminalu wartość zmiennej JAVA_HOME=D:\shell_variable_path

Uruchamiamy kontener w trybie interaktywnym z konsolą, wskazując -e JAVA_HOME. Następnie wewnątrz kontenera, wyświetlamy zawartość zmiennej.

$ docker run -it -e JAVA_HOME alpine sh
/ # $JAVA_HOME 
sh: D:\shell_variable_path

Widzimy, że wewnątrz kontenera została podstawiona wartość z hosta (a dokładnie z terminala hosta).

2. Wartości z pliku (env_file)

Tworzymy plik o dowolnej nazwie, a jego zawartość powinna opierać się o wpisach typu klucz=wartość. Dla przykładu, plik ten będzie miał nazwę my_env_file (bez żadnego rozszerzenia), a jego zawartość będzie następująca:

VARIABLE_NAME=variable_value

Uruchamiamy kontener z parametrem env (spowoduje wyświetlenie wszystkich zmiennych środowiskowych) wskazując na plik my_env_file:

$ docker run --env-file=my_env_file alpine env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=77929624bad0
VARIABLE_NAME=variable_value
HOME=/root

Możemy również wykorzystać to samo podejście w pliku docker-compose.yml, dodając wpis env_file: <ścieżka_do_pliku>

version: '3.2'

services:
  alpine:
    image: alpine
    env_file: my_env_file


Podsumowanie

Stosowanie zmiennych środowiskowych w Dockerze jest czymś naturalnym i często wykorzystywanym. Na przykładzie WordPressa widzimy, że nawet oficjalne obrazy, które możemy znaleźć na Docker Hubie stosują ten mechanizm. Warto go zatem znać i rozumieć jak działa.

Mam nadzieję, że nieco pomogłem zrozumieć Ci jak działają zmienne środowiskowe w Dockerze, lub bynajmniej post ten spowodował odświeżenie Twojej dotychczasowej wiedzy. Ważne, że w razie wątpliwości można tutaj wrócić i rozwiać swoje wątpliwości, do czego gorąco zachęcam!



.

Damian Naprawa

Praktykujący pasjonat konteneryzacji. Lubi dzielić się wiedzą, prowadząc warsztaty i szkolenia. Chętnie występuje również w roli prelegenta na meetupach i konferencjach. Uczestnik globalnego programu partnerskiego Docker Enablement. Pracuje z Dockerem na co dzień od kilku lat. Odpowiedzialny za tworzenie i utrzymanie systemów działających w oparciu o kontenery. Fan automatyzacji oraz podejścia "As a Code".

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *