![](/img/trans.png)
[英]No env variables in bash after exported with command in docker-compose
[英]How to make docker-compose ".env" file take precedence over shell env vars?
我希望我的 docker-compose.yml 文件使用與“docker-compose.yml”文件位於同一目錄中的“.env”文件來設置一些環境變量,並讓這些變量優先於設置的任何其他環境變量貝殼。 現在我有
$ echo $DB_USER
tommyboy
在我的 .env 文件中,我有
$ cat .env
DB_NAME=directory_data
DB_USER=myuser
DB_PASS=mypass
DB_SERVICE=postgres
DB_PORT=5432
我的 docker-compose.yml 文件中有這個...
version: '3'
services:
postgres:
image: postgres:10.5
ports:
- 5105:5432
environment:
POSTGRES_DB: directory_data
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: password
web:
restart: always
build: ./web
ports: # to access the container from outside
- "8000:8000"
environment:
DEBUG: 'true'
SERVICE_CREDS_JSON_FILE: '/my-app/credentials.json'
DB_SERVICE: host.docker.internal
DB_NAME: directory_data
DB_USER: ${DB_USER}
DB_PASS: password
DB_PORT: 5432
command: /usr/local/bin/gunicorn directory.wsgi:application --reload -w 2 -b :8000
volumes:
- ./web/:/app
depends_on:
- postgres
在我的 Python 3/Django 3 項目中,我的應用程序的 settings.py 文件中有這個
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ['DB_NAME'],
'USER': os.environ['DB_USER'],
'PASSWORD': os.environ['DB_PASS'],
'HOST': os.environ['DB_SERVICE'],
'PORT': os.environ['DB_PORT']
}
}
但是,當我使用“docker-compose up”運行我的項目時,我看到了
maps-web-1 | File "/usr/local/lib/python3.9/site-packages/django/db/backends/postgresql/base.py", line 187, in get_new_connection
maps-web-1 | connection = Database.connect(**conn_params)
maps-web-1 | File "/usr/local/lib/python3.9/site-packages/psycopg2/__init__.py", line 127, in connect
maps-web-1 | conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
maps-web-1 | psycopg2.OperationalError: FATAL: role "tommyboy" does not exist
似乎 Django 容器正在使用 shell 的 env var 而不是傳入的內容,我想知道是否有辦法讓 Python/Django 容器在根目錄中使用“.env”文件作為它的 env vars。
起初我以為我誤讀了您的問題,但我認為我最初的評論是正確的。 正如我之前提到的,本地 shell 環境通常會覆蓋.env
文件中的內容。 這允許您覆蓋命令行上的設置。 換句話說,如果您的.env
文件中有:
DB_USER=tommyboy
並且您想為單個 docker docker-compose up
調用覆蓋DB_USER
的值,您可以運行:
DB_USER=alice docker-compose up
這就是本地環境中的值優先的原因。
當將docker-compose
與存儲持久數據的東西一起使用時——比如 Postgres! -- 在使用用於配置容器的環境變量時,您偶爾會看到一些奇怪的行為。 考慮以下事件序列:
我們第一次使用.env
文件中的值運行docker-compose up
。
我們確認我們可以使用myuser
用戶連接到數據庫:
$ docker-compose exec postgres psql -U myuser directory_data psql (10.5 (Debian 10.5-2.pgdg90+1)) Type "help" for help. directory_data=#
我們通過輸入CTRL-C
來停止容器。
我們在環境變量中使用DB_USER
的新值啟動容器:
DB_USER=tommyboy docker-compose up
我們嘗試使用tommyboy
用戶名進行連接...
$ docker-compose exec postgres psql -U tommyboy directory_data psql: FATAL: role "tommyboy" does not exist
......它失敗了。
這里發生了什么?
用於配置 Postgres 的POSTGRES_*
環境變量僅在數據庫尚未初始化時才相關。 當您使用docker-compose
停止並重新啟動服務時,它不會創建新容器; 它只是重新啟動現有的。
這意味着在上述事件序列中,數據庫最初是使用myuser
用戶名創建的,並且在我們的環境中設置DB_USER
時第二次啟動它並沒有改變任何東西。
這里的解決方案是使用docker-compose down
命令,它會刪除容器...
docker-compose down
然后使用更新的環境變量創建一個新的:
DB_USER=tommyboy docker-compose up
現在我們可以按預期訪問數據庫:
$ docker-compose exec postgres psql -U tommyboy directory_data
psql (10.5 (Debian 10.5-2.pgdg90+1))
Type "help" for help.
directory_data=#
我無法提供比@larsks 提供的優秀答案更好的答案,但請讓我嘗試給你一些想法。
正如@larsks 還指出的那樣,任何 shell 環境變量都將優先於 docker-compose .env
文件中定義的那些環境變量。
在處理環境變量時,docker-compose 文檔中也說明了這一事實,強調我的:
您可以使用
.env
文件設置環境變量的默認值,Compose 會自動在項目目錄(Compose 文件的父文件夾)中查找該文件。 在 shell 環境中設置的值會覆蓋在.env
文件中設置的值。
這意味着,例如,提供這樣的 shell 變量:
DB_USER= tommyboy docker-compose up
將最終覆蓋您可以在.env
文件中定義的任何變量。
該問題的一種可能解決方案是嘗試直接使用.env
文件,而不是環境變量。
搜索有關您的問題的信息時,我遇到了這篇很棒的文章。
除其他外,除了解釋您的問題外,它還在帖子末尾的注釋中提到了一種基於使用django-environ
包的替代方法。
我不知道該庫,但它似乎提供了一種配置應用程序的替代方法,直接從配置文件中讀取配置:
import environ
import os
env = environ.Env(
# set casting, default value
DEBUG=(bool, False)
)
# Set the project base directory
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Take environment variables from .env file
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
# False if not in os.environ because of casting above
DEBUG = env('DEBUG')
# Raises Django's ImproperlyConfigured
# exception if SECRET_KEY not in os.environ
SECRET_KEY = env('SECRET_KEY')
# Parse database connection url strings
# like psql://user:pass@127.0.0.1:8458/db
DATABASES = {
# read os.environ['DATABASE_URL'] and raises
# ImproperlyConfigured exception if not found
#
# The db() method is an alias for db_url().
'default': env.db(),
# read os.environ['SQLITE_URL']
'extra': env.db_url(
'SQLITE_URL',
default='sqlite:////tmp/my-tmp-sqlite.db'
)
}
#...
如果需要,您似乎也可以混合環境中定義的變量。
可能python-dotenv將允許您遵循類似的方法。
當然,值得一提的是,如果您決定使用這種方法,您需要使.env
文件可訪問到您的 docker-compose Web 服務和相關容器,可能是掛載和附加卷或將.env
文件復制到web
目錄您已經安裝為卷。
您仍然需要處理 PostgreSQL 容器配置,但以某種方式它可以幫助您實現您在評論中指出的目標,因為您可以使用相同的.env
文件(當然,一個重復的文件)。
根據您的評論,另一種可能的解決方案可能是使用 Docker 機密。
例如,在 Kubernetes 中,secrets 的工作方式與官方文檔中的解釋類似:
就 Docker Swarm 服務而言,秘密是一團數據,例如密碼、SSH 私鑰、SSL 證書或其他不應通過網絡傳輸或未加密存儲在 Dockerfile 或應用程序中的數據源代碼。 您可以使用 Docker 機密集中管理這些數據,並將其安全地傳輸到需要訪問它的容器。 秘密在傳輸過程中被加密,在 Docker 群中處於靜止狀態。 一個給定的秘密只能被那些被授予顯式訪問權限的服務訪問,並且只有在這些服務任務正在運行時才可以訪問。
簡而言之,它提供了一種跨 Docker Swarm 服務存儲敏感數據的便捷方式。
重要的是要了解 Docker 機密僅在使用Docker Swarm 模式時可用。
Docker Swarm 是 Docker 提供的一種編排服務,與 Kubernetes 類似,當然也有區別。
假設您在 Swarm 模式下運行 Docker,您可以根據官方 docker-compose docker secrets 示例以類似於以下方式部署您的 compose 服務:
version: '3'
services:
postgres:
image: postgres:10.5
ports:
- 5105:5432
environment:
POSTGRES_DB: directory_data
POSTGRES_USER: /run/secrets/db_user
POSTGRES_PASSWORD: password
secrets:
- db_user
web:
restart: always
build: ./web
ports: # to access the container from outside
- "8000:8000"
environment:
DEBUG: 'true'
SERVICE_CREDS_JSON_FILE: '/my-app/credentials.json'
DB_SERVICE: host.docker.internal
DB_NAME: directory_data
DB_USER_FILE: /run/secrets/db_user
DB_PASS: password
DB_PORT: 5432
command: /usr/local/bin/gunicorn directory.wsgi:application --reload -w 2 -b :8000
volumes:
- ./web/:/app
depends_on:
- postgres
secrets:
- db_user
secrets:
db_user:
external: true
請注意以下事項。
我們在秘密部分定義了一個名為db_user
的secrets
。
這個秘密可以基於一個文件或從標准 in 計算,例如:
echo "tommyboy" | docker secret create db_user -
秘密應該暴露給需要它的每個容器。
在 Postgres 的情況下,如官方 Postgres docker image description中的Docker secrets
部分所述,您可以使用 Docker secrets 定義POSTGRES_INITDB_ARGS
、 POSTGRES_PASSWORD
、 POSTGRES_USER
和POSTGRES_DB
的值:秘密變量的名稱是與帶有后綴_FILE
的普通文件相同。
在我們的用例中,我們定義了:
POSTGRES_USER_FILE: /run/secrets/db_user
在 Django 容器的情況下,此功能不支持開箱即用,但由於您可以根據需要編輯settings.py
,例如在這篇簡單但很棒的文章中建議您可以使用幫助程序在settings.py
文件中讀取所需值的函數,例如:
import os
def get_secret(key, default):
value = os.getenv(key, default)
if os.path.isfile(value):
with open(value) as f:
return f.read()
return value
DB_USER = get_secret("DB_USER_FILE", "")
# Use the value to configure your database connection parameters
存儲數據庫密碼可能更有意義,但它也可能是數據庫用戶的有效解決方案。
請考慮查看這篇出色的文章。
基於問題似乎是由 Django 容器中環境變量的更改引起的,您可以嘗試的最后一件事如下。
您的settings.py
文件的唯一要求是使用您的配置聲明不同的全局變量。 但它並沒有說明如何閱讀它們:實際上,我在答案中暴露了不同的方法,而且畢竟是 Python,您可以使用該語言來滿足您的需求。
此外,重要的是要了解,除非在 Dockerfile 中更改任何變量,否則在創建Postgres 和 Django 容器時,將收到完全相同的.env
文件和完全相同的配置。
考慮到這兩件事,您可以嘗試在settings-py
文件中創建所提供環境的 Django 容器本地副本,並在重新啟動之間或在導致變量更改的任何原因之間使用它。
在你的settings.py
(請原諒我代碼的簡單性,我希望你明白):
import os
import ast
env_vars = ['DB_NAME', 'DB_USER', 'DB_PASS', 'DB_SERVICE', 'DB_PORT']
if not os.path.exists('/tmp/.env'):
with open('/tmp/.env', 'w') as f:
for env_var in env_vars:
f.write(env_var)
f.write('=')
f.write(os.environ[env_var])
f.write('\n')
with open('/tmp/.env') as f:
cached_env_vars = f.read()
cached_env_vars_dict = ast.literal_eval(cached_env_vars)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': cached_env_vars_dict['DB_NAME'],
'USER': cached_env_vars_dict['DB_USER'],
'PASSWORD': cached_env_vars_dict['DB_PASS'],
'HOST': cached_env_vars_dict['DB_SERVICE'],
'PORT': cached_env_vars_dict['DB_PORT']
}
#...
}
我認為上述任何一種方法都更好,但肯定會確保環境變量在環境變化和容器重啟時的一致性。
shell 中的值優先於 .env 文件中指定的值。
如果您在 shell 中將 TAG 設置為不同的值,則圖像中的替換將使用該值:
export TAG=v2.0
docker compose convert
version: '3'
services:
web:
image: 'webapp:v1.5'
更多詳情請參考鏈接: https ://docs.docker.com/compose/environment-variables/
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.