Python环境变量本质:从os.environ到Docker/K8s安全实践
1. 项目概述环境变量不是“配置文件”而是进程的呼吸系统Python 环境变量不是你写在.env里的几行键值对也不是config.py里定义的常量——它是 Python 进程启动时从操作系统继承的一套“先天设定”是解释器睁开眼看到的第一个世界。我带过十几期 Python 工程师培训每次讲到os.environ总有学员下意识去翻自己的settings.py结果调试三天找不到为什么DEBUGTrue不生效也有团队把DATABASE_URL写死在代码里上线后被安全审计打回重做。这背后根本不是代码问题而是对环境变量本质的理解偏差。核心关键词——Python 环境变量、os.environ、PATH、PYTHONPATH、venv 隔离、Docker 构建上下文、敏感信息管理——全部指向一个事实它不只影响 Python 脚本怎么运行更决定你的应用在开发、测试、CI/CD、生产不同阶段是否可复现、可审计、可迁移。它解决的是“同一份代码在我的电脑上能跑在服务器上报错 ModuleNotFoundError在 CI 里又提示 PermissionError”的经典三连问。适合三类人直接抄作业刚脱离python script.py单文件模式、开始接触 Flask/Django 项目的中级开发者负责搭建 CI 流水线、部署容器化服务的 DevOps 同事以及需要向审计或合规部门解释“密钥为何没硬编码”的技术负责人。我试过用print(os.environ)打印出 83 行变量但真正影响 Python 行为的不到 15 个我也踩过坑在 Dockerfile 里用ENV设置了PYTHONUNBUFFERED1却忘了在docker-compose.yml的environment字段里重复声明导致日志在 Kubernetes Pod 里全卡住不输出。这些不是玄学是操作系统进程模型与 Python 启动机制共同作用的结果。接下来我会带你一层层剥开它从哪来、被谁读、怎么改、改了之后谁会感知、改错之后哪里会崩——不是罗列文档而是还原真实战场上的决策链。2. 环境变量的本质与作用域进程树、继承链与不可见的“空气”2.1 它不是 Python 的特性而是操作系统的遗产环境变量Environment Variables压根不是 Python 发明的它是 Unix/Linux 和 Windows 操作系统为每个进程提供的“初始参数包”。当你在终端敲下python app.pyShell比如 bash 或 zsh会 fork 出一个新进程然后 exec 调用 Python 解释器。在这个 exec 过程中Shell 会把自己当前的环境变量副本原封不动地传递给子进程——也就是 Python 解释器。这个过程和你传参python app.py --port 8000完全不同命令行参数是显式、一次性的而环境变量是隐式、全局性的且会继续向下传递给 Python 代码里subprocess.Popen()启动的任何子进程。提示你可以用ps -ef | grep python查看进程树再用cat /proc/PID/environ | tr \0 \nLinux或Get-Process -Id PID | Select-Object -ExpandProperty StartInfoPowerShell验证环境变量是否真的被继承。这不是理论是每个进程都携带的“出生证明”。Python 通过os.environ这个类字典对象暴露这套机制。它底层调用的是 C 标准库的getenv()和putenv()所以它的行为完全受操作系统约束在 Linux/macOS 上区分大小写在 Windows 上不区分修改os.environ只影响当前 Python 进程及其后续子进程不会反向写回父 Shell而os.putenv()在某些 Python 版本中甚至不刷新os.environ缓存必须手动os.environ[key] value才能确保一致性。2.2 关键变量的“职责地图”哪些真管用哪些是历史包袱不是所有以PYTHON开头的变量都有效。Python 官方文档明确支持的只有 7 个核心变量其余多为社区约定或第三方库扩展。下面这张表是我从 CPython 源码Modules/main.c和PyConfig结构体和 12 个主流框架源码中交叉验证得出的实战清单变量名是否由 CPython 直接解析典型用途修改时机建议实测风险等级PYTHONPATH✅ 是类似 Java 的 CLASSPATH添加模块搜索路径sys.path前置启动前设置避免运行时sys.path.insert(0, ...)⚠️ 中路径冲突易引发隐式覆盖PYTHONHOME✅ 是指定 Python 安装根目录影响标准库加载极少手动设多用于嵌入式 Python 或多版本共存场景❗ 高设错直接ImportError: No module named sitePYTHONIOENCODING✅ 是控制sys.stdin/stdout/stderr的默认编码如utf-8:surrogateescape在非 UTF-8 终端如 Windows CMD必须显式设置⚠️ 中中文乱码的根源之一PYTHONUNBUFFERED✅ 是强制 stdout/stderr 无缓冲1或行缓冲0CI/CD 日志采集、Docker 容器内必开✅ 低纯输出行为无副作用PYTHONFAULTHANDLER✅ 是启用faulthandler模块在崩溃时打印 Python traceback调试段错误segfault时开启✅ 低仅影响崩溃日志PYTHONMALLOC✅ 是指定内存分配器malloc,pymalloc,debug性能调优或内存泄漏分析专用❗ 高debug模式性能下降 50%PYTHONDONTWRITEBYTECODE✅ 是禁止生成.pyc字节码文件容器镜像构建时减小体积或 NFS 共享目录避免权限问题✅ 低仅影响磁盘 I/OPATH❌ 否但 Python 依赖它操作系统查找可执行文件的路径pip,python3.9等必须在 Shell 层设置Python 内无法安全修改⚠️ 中os.environ[PATH] :/new/path易引入空路径LD_LIBRARY_PATH(Linux) /DYLD_LIBRARY_PATH(macOS)❌ 否但 C 扩展依赖它指定动态链接库搜索路径影响numpy,Pillow等 C 扩展在venv激活脚本或 Dockerfile 中预设❗ 高路径错误导致ImportError: libxxx.so not found注意PYTHONPATH是最常被误用的。很多人以为它能替代pip install -e .但实际效果是“临时打补丁”——它会让import mypackage成功却绕过了 pip 的依赖解析和版本锁定导致pip list看不到该包pip freeze requirements.txt也漏掉它。我在一个金融项目里见过开发用PYTHONPATH加了本地调试版risk_engine测试环境却用 pip 安装了线上版结果风控计算逻辑静默降级直到审计发现数值偏差才暴露。2.3 作用域的三层现实Shell → venv → 进程谁覆盖谁环境变量的作用域不是扁平的而是嵌套的三层结构每一层都可能覆盖上一层Shell 层全局基础你在~/.bashrc或/etc/environment里设置的变量是所有后续进程的起点。例如export PATH/usr/local/bin:$PATH让系统优先找自编译的工具。虚拟环境层隔离关键venv激活时bin/activate脚本会修改PATH前置venv/bin并可选设置VIRTUAL_ENV。但注意venv本身不自动管理其他环境变量PYTHONPATH、DATABASE_URL等仍继承自 Shell。这就是为什么source venv/bin/activate后echo $PATH变了但echo $DATABASE_URL还是空的。进程层最终生效Python 解释器启动后os.environ固化。此时os.environ[PATH]是 Shell venv 修改后的结果而os.environ[MY_VAR]就是 Shell 传进来的原始值——除非你在 Python 代码里主动os.environ[MY_VAR] new_value。实操验证法# 终端 A设置基础变量 $ export DEBUGshell echo $DEBUG # 输出 shell $ python -c import os; print(os.environ.get(DEBUG)) # 输出 shell # 终端 B激活 venv 后 $ source venv/bin/activate (venv) $ export DEBUGvenv echo $DEBUG # 输出 venv (venv) $ python -c import os; print(os.environ.get(DEBUG)) # 输出 venv # 终端 C在 Python 里修改 (venv) $ python -c import os os.environ[DEBUG] python print(in python:, os.environ[DEBUG]) import subprocess subprocess.run([python, -c, import os; print(\in child:\, os.environ.get(\DEBUG\))]) # 输出 # in python: python # in child: python ← 子进程继承了 Python 层的修改这个实验说明变量修改是单向向下传递的且venv激活只是 Shell 层操作不改变 Python 进程的“基因”。很多团队把密钥写在venv/bin/activate里以为激活就安全其实只要有人deactivate再export SECRET_KEYxxx整个环境就污染了。3. 核心变量深度解析从原理到避坑的完整链条3.1PYTHONPATH模块导入的“暗道”也是依赖混乱的温床PYTHONPATH的工作原理非常直白CPython 启动时会将该变量值按:Linux/macOS或;Windows分割把每个路径插入sys.path列表的最前面。这意味着它拥有最高优先级能覆盖标准库、site-packages 里的同名模块。# 示例当 PYTHONPATH/home/user/mylib:/opt/shared 时 import sys print(sys.path[:3]) # 输出类似[/home/user/mylib, /opt/shared, /usr/lib/python3.9]但问题来了假设/home/user/mylib/utils.py和pip install requests安装的requests/utils.py都存在import utils会导入哪个答案是前者——因为PYTHONPATH路径在sys.path最前。这在调试时很爽但在生产环境就是灾难你本地测试用PYTHONPATH注入了 mock 版database.pyCI 流水线没设该变量结果跑的是真实数据库连接。避坑三原则永远不用PYTHONPATH替代pip install -e .-e模式会创建.egg-link文件让 pip 完整管理依赖关系PYTHONPATH则是“黑盒注入”。路径必须绝对禁止相对路径export PYTHONPATH./src在不同工作目录下行为不一致os.getcwd()改变后PYTHONPATH失效。在 Docker 中显式清理即使你不设PYTHONPATH基础镜像如python:3.9-slim可能自带值。构建时加一行ENV PYTHONPATH彻底清空强制走标准流程。我处理过一个遗留系统PYTHONPATH被设为/app/src:/app/libs但pip list显示myapp1.0.0实际运行的却是/app/src/myapp/下的开发版。排查方法很简单在入口脚本开头加print(sys.path:, sys.path)立刻暴露路径污染。3.2PATH与PYTHONUNBUFFERED容器化部署的生死线在 Docker 和 Kubernetes 场景下PATH和PYTHONUNBUFFERED的组合决定了日志是否可见、命令能否执行。PATH的陷阱Alpine 镜像python:3.9-alpine默认PATH/usr/local/bin:/usr/bin:/bin但很多 Python 包的 CLI 工具如black,isort安装在/usr/local/bin而pip install默认装到venv/bin。如果你在Dockerfile里这样写RUN pip install black CMD [black, --check, .]会报executable file not found in $PATH因为black被装到了venv/bin/black但CMD没激活 venv。正确做法是ENV PATH/app/venv/bin:$PATH # 显式前置 venv bin RUN pip install black CMD [black, --check, .]PYTHONUNBUFFERED1的必要性Python 默认对stdout做行缓冲line-buffered但当 stdout 连接到管道或文件如docker logs、K8s 的kubectl logs时会切换为全缓冲fully-buffered。这意味着print(starting...)可能卡在内存里几秒不输出直到缓冲区满或进程退出。PYTHONUNBUFFERED1强制禁用缓冲让日志实时流式输出。我在一个支付网关服务里遇到过超时日志延迟 15 秒才出现导致故障定位时间翻倍。实测对比Docker 容器内# 未设 PYTHONUNBUFFERED $ docker run --rm python:3.9-slim python -c import time for i in range(3): print(flog {i}) time.sleep(2) # 执行后立即返回无输出缓冲未刷 # 设 PYTHONUNBUFFERED1 $ docker run --rm -e PYTHONUNBUFFERED1 python:3.9-slim python -c import time for i in range(3): print(flog {i}) time.sleep(2) # 每 2 秒输出一行log 0 → log 1 → log 2注意-u参数python -u script.py等价于PYTHONUNBUFFERED1但环境变量方式更利于容器编排如docker-compose.yml的environment字段。3.3 敏感信息管理为什么os.environ是密钥的“合法外衣”把数据库密码、API Key 写在代码里是初级错误但用.env文件就安全了吗不完全是。.env文件本身是普通文本如果误提交到 Git、或容器镜像层未清理依然泄露。os.environ的价值在于它提供了一层运行时解耦密钥不随代码分发而由部署环境注入。但这里有个致命误区很多人以为python-dotenv加载.env就等于安全。真相是load_dotenv()只是把文件内容读进os.environ它不加密、不权限控制、不审计日志。真正的安全链条是密钥存储HashiCorp Vault / AWS Secrets Manager → 部署时注入K8s Secret mount / Docker --secret → Python 读取os.environ.get(DB_PASSWORD)我参与过一个医疗 SaaS 项目审计要求密钥访问必须有完整审计日志。我们放弃.env改用 K8s Secret# k8s-secret.yaml apiVersion: v1 kind: Secret metadata: name: db-secret type: Opaque data: DB_HOST: aG9tZS1kYi5leGFtcGxlLmNvbQ # base64 encoded DB_PASSWORD: cGFzc3dvcmQxMjM # base64 encoded --- # deployment.yaml envFrom: - secretRef: name: db-secret这样密钥只存在于 K8s etcd 加密存储中Pod 启动时由 kubelet 注入os.environ且每次注入都有审计日志记录。os.environ在这里不是存储介质而是安全策略的执行接口。4. 实操全流程从本地开发到生产部署的 7 个关键节点4.1 本地开发Shell 配置与 venv 激活的黄金组合本地环境最容易失控因为每个人 Shell 配置不同。我的标准流程是Shell 层统一基础变量~/.bashrc或~/.zshrc# 基础安全禁用 byte code强制 UTF-8 export PYTHONDONTWRITEBYTECODE1 export PYTHONIOENCODINGutf-8 # 开发便利添加常用工具路径避免 pip install --user export PATH$HOME/.local/bin:$PATH # 项目无关的通用变量如代理但注意不放敏感信息 # export HTTP_PROXYhttp://localhost:8080项目级.envrc使用 direnvdirenv是 Shell 层的环境变量管理神器进入目录自动加载离开自动清理。比source .env安全得多不会残留变量。# 项目根目录下的 .envrc # 加载 .env 文件需先安装 dotenv 命令pipx install dotenv-cli dotenv # 设置项目专属变量 export FLASK_ENVdevelopment export PYTHONPATH$(pwd)/src:$(pwd)/tests # 导出给子进程重要否则 Python 看不到 export FLASK_ENV PYTHONPATH启用direnv allow。效果cd进项目目录变量自动生效cd ..变量自动清除。再也不用担心export DEBUG1污染全局。venv 激活后验证激活 venv 后立即检查关键变量$ source venv/bin/activate (venv) $ python -c import os, sys print(PATH:, os.environ[PATH].split(:)[:2]) print(PYTHONPATH:, os.environ.get(PYTHONPATH, NOT SET)) print(sys.path[0]:, sys.path[0]) # 确保 PATH 前置 venv/binPYTHONPATH 指向项目 src且 sys.path[0] 与 PYTHONPATH 一致实操心得永远不要在venv/bin/activate里写export SECRET_KEYxxx。activate脚本是 Shell 层的一旦deactivate变量还在 Shell 里别人source你的脚本就全暴露了。密钥必须用direnv.envrc且.envrc加入.gitignore。4.2 CI/CD 流水线GitHub Actions 中的环境变量安全实践GitHub Actions 的环境变量有三个来源权限和安全性逐级降低来源设置位置是否加密是否可被 PR 分支读取推荐用途envJob 级jobs.job_id.env❌ 否✅ 是公共配置PYTHON_VERSION3.9,DJANGO_SETTINGS_MODULEprodsecretsJob 级jobs.job_id.steps[*].envsecrets.*✅ 是❌ 否PR 分支无法访问密钥DB_PASSWORD,AWS_ACCESS_KEY_IDinputsReusable Workflowon.workflow_call.inputs✅ 是❌ 否参数化流水线deploy_env: production典型安全配置ci.ymlname: CI Pipeline on: [push, pull_request] jobs: test: runs-on: ubuntu-latest env: # 公共变量明文但无害 PYTHONUNBUFFERED: 1 PYTHONDONTWRITEBYTECODE: 1 # 项目配置 DJANGO_SETTINGS_MODULE: config.settings.test steps: - uses: actions/checkoutv4 - name: Set up Python uses: actions/setup-pythonv4 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements/test.txt pip install pytest-cov black - name: Run tests # secrets 只能通过 env 传入 step不能在 run 里 echo env: DB_URL: ${{ secrets.TEST_DB_URL }} # 加密PR 无法读取 REDIS_URL: ${{ secrets.TEST_REDIS_URL }} run: pytest --covmyapp tests/关键技巧secrets变量不能直接在run脚本里引用如echo ${{ secrets.DB_URL }}会报错必须通过env字段注入。所有secrets在日志中自动被***替换但如果你在 Python 代码里print(os.environ[DB_URL])它依然会输出明文所以os.environ.get()后要立即擦除del os.environ[DB_URL]。用actions/github-script动态生成环境变量如根据分支名设ENVstaging避免硬编码。4.3 Docker 构建多阶段构建中的环境变量净化Docker 构建是环境变量污染的重灾区。常见错误在build阶段用ARG传密钥结果密钥被固化进镜像层。错误示范密钥泄露# ❌ 危险密钥进入镜像层 ARG API_KEY RUN pip install mypackage --key $API_KEY # API_KEY 存在 build cache 中正确方案多阶段 runtime 注入# 构建阶段纯净环境不接触密钥 FROM python:3.9-slim as builder WORKDIR /app COPY requirements.txt . RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt # 运行阶段最小镜像密钥 runtime 注入 FROM python:3.9-slim WORKDIR /app COPY --frombuilder /app/wheels /wheels COPY --frombuilder /usr/share/ca-certificates /usr/share/ca-certificates RUN pip install --no-cache /wheels/*.whl # 清理构建残留显式重置环境变量 ENV PYTHONUNBUFFERED1 ENV PYTHONDONTWRITEBYTECODE1 ENV PATH/app/venv/bin:$PATH # ❌ 不设任何密钥相关变量 COPY entrypoint.sh . RUN chmod x entrypoint.sh ENTRYPOINT [./entrypoint.sh]entrypoint.sh负责 runtime 初始化#!/bin/sh # entrypoint.sh set -e # 从文件或 K8s Secret 读取密钥安全 if [ -f /run/secrets/db_password ]; then export DB_PASSWORD$(cat /run/secrets/db_password) fi # 执行主命令 exec $启动时# Docker CLI docker run --secret db_passwordmyrealpassword myapp # Docker Compose services: app: image: myapp secrets: - db_password secrets: db_password: file: ./db_password.txt # 本地开发用这样密钥只存在于容器运行时内存中镜像层完全干净。docker history myapp查看各层没有一行包含密钥。4.4 生产部署KubernetesSecret Mount 与 ConfigMap 的协同K8s 中os.environ的来源有且仅有两个env字段明文和envFrom来自 Secret/ConfigMap。最佳实践是ConfigMap 存配置Secret 存密钥。配置文件configmap.yamlapiVersion: v1 kind: ConfigMap metadata: name: app-config data: # 明文配置可 Git 管理 APP_ENV: production LOG_LEVEL: INFO DATABASE_URL: postgresql://user:db:5432/app --- # deployment.yaml envFrom: - configMapRef: name: app-config密钥文件secret.yamlapiVersion: v1 kind: Secret metadata: name: app-secrets type: Opaque data: # base64 编码Git 可存但建议用外部 Vault DATABASE_PASSWORD: cGFzc3dvcmQxMjM JWT_SECRET_KEY: c2VjcmV0X2tleV9mb3JfanR3 --- # deployment.yaml envFrom: - secretRef: name: app-secretsPython 代码安全读取import os from typing import Optional def get_secret(key: str, default: Optional[str] None) - str: 安全读取密钥读取后立即从内存擦除 value os.environ.get(key, default) if value is not None and key in [DATABASE_PASSWORD, JWT_SECRET_KEY]: # 从 os.environ 删除防止意外泄露 os.environ.pop(key, None) return value # 使用 DB_PASSWORD get_secret(DATABASE_PASSWORD) JWT_KEY get_secret(JWT_SECRET_KEY)注意os.environ.pop()只删除当前进程的副本不影响 K8s Secret 本身。这是防御性编程防的是print(os.environ)或异常堆栈里意外暴露。5. 常见问题与排查技巧实录那些年我们踩过的坑5.1 问题速查表症状、原因、解决方案症状可能原因快速验证命令解决方案ModuleNotFoundError: No module named xxxPYTHONPATH路径错误venv未激活sys.path顺序错python -c import sys; print(\n.join(sys.path))检查PYTHONPATH是否含绝对路径确认which python指向 venv用pip install -e .替代PYTHONPATHUnicodeEncodeError: ascii codec cant encode characterPYTHONIOENCODING未设终端 locale 非 UTF-8localepython -c import sys; print(sys.stdout.encoding)export PYTHONIOENCODINGutf-8export LANGen_US.UTF-8Docker 容器内pip install报Connection refusedHTTP_PROXY环境变量被继承但代理服务不在容器内docker run --rm python:3.9-slim env | grep -i proxy构建时ENV HTTP_PROXY或在pip install命令中显式--proxyos.environ.get(VAR)返回None但echo $VAR有值变量未exportBash 中VARvalue不导出Python 进程未继承bash -c VARtest; echo $VAR; python -c import os; print(os.environ.get(\VAR\))export VARvalue或在启动命令前加VARvalue python app.pyCI 流水线里pytest找不到测试文件PYTHONPATH未设sys.path不含tests/目录python -c import sys; print([p for p in sys.path if tests in p])在 CI 脚本中export PYTHONPATH$(pwd)/tests:$(pwd)/srcK8s Pod 启动失败日志空白PYTHONUNBUFFERED未设日志缓冲未刷出kubectl logs pod --previous看上次崩溃日志在 Deployment 的env中加PYTHONUNBUFFERED15.2 独家排查技巧三步定位环境变量污染源当变量行为诡异如DEBUGTrue时日志不输出用这套组合拳第一步进程级快照确定当前值# 获取 Python 进程的完整环境Linux PID$(pgrep -f python app.py) cat /proc/$PID/environ | tr \0 \n | grep -E (DEBUG|DATABASE|PYTHON)第二步Shell 层溯源查谁设的# 查看当前 Shell 的所有 export 命令 declare -p | grep -E (DEBUG|DATABASE) # 检查配置文件加载顺序bash echo $BASH_SOURCE grep -n export DEBUG ~/.bashrc ~/.bash_profile ~/.profile 2/dev/null第三步Python 运行时审计看是否被篡改在 Python 入口文件如app.py开头插入import os import sys # 记录初始环境 initial_env {k: v for k, v in os.environ.items() if k in [DEBUG, DATABASE_URL, PYTHONPATH]} # 启动前打印 print( ENV ON START ) for k, v in initial_env.items(): print(f{k}{repr(v)}) # 启动后再次检查防中间件篡改 def audit_env(): current {k: v for k, v in os.environ.items() if k in initial_env} changed {k: (initial_env[k], current[k]) for k in current if initial_env[k] ! current[k]} if changed: print( ENV CHANGED ) for k, (old, new) in changed.items(): print(f{k}: {repr(old)} → {repr(new)})这套方法帮我在一个微服务项目里揪出问题gunicorn的preload模式会在主进程加载代码而worker进程继承环境但某个中间件在worker初始化时偷偷os.environ[DEBUG] false导致日志开关失效。审计日志直接暴露篡改点。5.3 高级技巧用strace追踪环境变量读取当怀疑第三方库如psycopg2读取了错误的环境变量用strace抓系统调用# 追踪 Python 进程的 getenv 调用 strace -e tracegetenv -f python -c import psycopg2; psycopg2.connect() 21 | grep -i password输出类似[pid 12345] getenv(PGPASSWORD) nil [pid 12345] getenv(PGHOST) localhost这能确认库是否真的读了PGPASSWORD还是你设错了变量名如DB_PASSWORDvsPGPASSWORD。6. 工具链与自动化让环境变量管理不再靠人肉记忆6.1direnvdotenv本地开发的黄金搭档direnv解决“进入目录自动加载离开自动清理”dotenv解决“.env 文件格式解析”。组合起来本地环境变量管理就闭环了。安装与配置# macOS brew install direnv echo eval $(direnv hook zsh) ~/.zshrc # Linux curl -sfL https://direnv.net/install.sh | bash echo eval $(direnv hook bash) ~/.bashrc项目级.envrc模板# 加载 .env推荐用 dotenv-cli比 python-dotenv 更轻量 if has dotenv-cli; then dotenv else echo Warning: dotenv-cli not installed. Install with: pipx install dotenv-cli fi # 项目专属变量 export PYTHONPATH$(pwd)/src:$(pwd)/tests export FLASK_APPapp:create_app export FLASK_ENVdevelopment # 导出给子进程关键 export PYTHONPATH FLASK_APP FLASK_ENV # 安全警告检测 .env 是否含敏感信息 if [[ -f .env ]] grep -q SECRET\|PASSWORD\|KEY .env; then echo ⚠️ WARNING: .env contains sensitive keys! Ensure its in .gitignore fi启用direnv allow。效果cd进项目变量自动生效cd ..变量自动清除。.env文件可 Git