Repository: promptfoo/promptfoo
Issue: Environment Variable Interpolation Not Working in YAML Configuration
Root cause: Configuration Error

###**Issue Description**

**Problem**:
Environment variable interpolation with ${VAR} syntax doesn't work in YAML configuration files, forcing users to hardcode sensitive API keys directly in configuration files, which creates security risks.

**Expected Behavior**
Environment variables should be properly interpolated in YAML configuration files using either ${VAR} or {{env.VAR}} syntax, allowing secure handling of API keys and other sensitive data.
Actual Behavior:
${HTTP_API_KEY} results in "Unknown api key : null" error
{{env.HTTP_API_KEY}} doesn't interpolate the variable value
Only hardcoded values work, creating security vulnerabilities

**Environment**
promptfoo version: 0.115.1
Deployment: Kubernetes with Helm chart

**Steps to Reproduce**
- Set environment variable for pod via env: HTTP_API_KEY="your-api-key"
- Verify variable is available: echo $HTTP_API_KEY ✅ (works)
- Verify variable is available in Node.js: node -e "console.log(process.env.HTTP_API_KEY)" ✅ (works)
- Edit YAML config with environment variable reference at UI:
```
# yaml-language-server: $schema=https://promptfoo.dev/config-schema.json
description: ''
providers:
  - id: http: gpt-4o
    config:
      url: https://api.example.com/chat/completions
      headers:
        Content-Type: application/json
        Api-Key: ${HTTP_API_KEY}  # This doesn't work
      # ... rest of config
```
- Push button "Run eval"
Result: "api key : null" error

<img width="1456" alt="Image" src="https://github.com/user-attachments/assets/b1a74931-e044-4462-a998-b88f5576e92f" />


**❌ What I've tried and these don't work:**
Attempt 1: Shell-style interpolation
Api-Key: ${HTTP_API_KEY}

Attempt 2: Nunjucks template syntax
Api-Key: "{{env.HTTP_API_KEY}}"

Attempt 3: Alternative Nunjucks syntax
Api-Key: {{env.HTTP_API_KEY}}

**✅ These work but are not secure:**
Api-Key: "sk-your-actual-api-key-here"

Hi @LukashevichVitali, since you're **self-hosting with Kubernetes/Helm**, you likely have`PROMPTFOO_SELF_HOSTED=true` set. In this mode, access to OS environment variables (`process.env`) in templates is **disabled by default** for security reasons. To enable access, add the following to your **Kubernetes deployment**:

```yaml
env:
  - name: PROMPTFOO_DISABLE_TEMPLATE_ENV_VARS
    value: "false"
```

This will allow {{env.HTTP_API_KEY}} to correctly resolve using your OS environment variables. I also created https://github.com/promptfoo/promptfoo/pull/4620 so you can specify env vars in the `env:` block of your config without changing this value.  Please let me know if this works! 



> `PROMPTFOO_SELF_HOSTED`

Hi @mldangelo, thanks for the quick reply!

Unfortunately the suggested fix didn’t work for me and return error:
```
An error occurred: [
  {
    "code": "invalid_type",
    "expected": "string",
    "received": "object",
    "path": [
      "headers",
      "Api-Key"
    ],
    "message": "Expected string, received object"
  }
]
```

Maybe most likely because the Docker image "ghcr.io/promptfoo/promptfoo:0.115.3" has not been published yet. I had to run my tests with "0.115.2" and ran into the following problem.
Inside the container the CLI reports a different version than the image tag "0.115.2" (same with tag "0.115.1"):
```
promptfoo --version

=========================================================================================================================================================
⚠️ The current version of promptfoo 0.115.1 is lower than the latest available version 0.115.3.

Please run npx promptfoo@latest or npm install -g promptfoo@latest to update.
=========================================================================================================================================================

0.115.1
```

So:

Image tag 0.115.2 contains promptfoo 0.115.1.
Image tag 0.115.1 contains promptfoo 0.115.0.
Image tag 0.115.3 is not yet available on GHCR, so it can’t be tested.
For completeness I’m attaching the configs I’m using (let me know if anything else is needed):

**promptfooconfig.yaml**
```
# yaml-language-server: $schema=https://promptfoo.dev/config-schema.json
description: ''
extensions: []
prompts:
  - raw: >-
      Write a short, fun story about a {{animal}} going on an adventure in
      {{location}}. Make it entertaining and suitable for children.
    label: >-
      Write a short, fun story about a {{animal}} going on an adventure in
      {{location}}. Make it entertaining and suitable for children.
providers:
  - id: http:gpt-4o
    config:
      url: >-
        https://api.example.com/chat/gpt-4o-2024-08-06
      headers:
        Content-Type: application/json
        Api-Key: {{env.HTTP_API_KEY}}
      method: POST
      body:
        model: gpt-4o-2024-08-06
        temperature: 1
        top_p: 1
        'n': 1
        stream: false
        response_format:
          type: json_object
        messages:
          - role: user
            content: '{{prompt}}'
      transformResponse: |
        function(response) {
          return JSON.parse(response.choices[0].message.content);
        }
  - id: http:gpt-o3-mini
    config:
      url: >-
        https://api.example.com/chat/o3-mini-2025-01-31
      headers:
        Content-Type: application/json
        Api-Key: {{env.HTTP_API_KEY}}
      method: POST
      body:
        model: o3-mini-2025-01-31
        temperature: 1
        top_p: 1
        'n': 1
        stream: false
        response_format:
          type: json_object
        messages:
          - role: user
            content: '{{prompt}}'
      transformResponse: |
        function(response) {
          return JSON.parse(response.choices[0].message.content);
        }
scenarios: []
tests:
  - description: Fun animal adventure story
    vars:
      animal: penguin
      location: tropical island
    assert:
      - type: contains-any
        value:
          - penguin
          - adventure
          - tropical
          - island
      - type: llm-rubric
        value: >-
          Is this a fun, child-friendly story featuring a penguin on a tropical
          island adventure?


          Criteria:

          1. Does it mention a penguin as the main character?

          2. Does the story take place on a tropical island?

          3. Is it entertaining and appropriate for children?

          4. Does it have a sense of adventure?
evaluateOptions: {}
defaultTest: {}

```

**helm-chart-user-supplied-values-only.yaml**
```
configDir: /app/data
domainName: example.com
httpApiKey: <format like this 32jsk901....>
image:
  pullPolicy: IfNotPresent
  repository: ghcr.io/promptfoo/promptfoo
  tag: 0.115.2
nodeSelector:
  role: workers
persistentVolumesClaims:
- accessModes:
  - ReadWriteOnce
  name: promptfoo
  size: 10Gi
  storageClassName: gp3-retain
podSecurityContext:
  fsGroup: 1000
resources:
  limits:
    cpu: 1000m
    memory: 1024Mi
securityContext:
  runAsGroup: 1000
  runAsNonRoot: true
  runAsUser: 1000
service:
  port: 3000
  type: ClusterIP
volumeMounts:
- mountPath: /app/data
  name: config
```

**deployment.yaml**
```
apiVersion: apps/v1
kind: Deployment
metadata:
  name: promptfoo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: promptfoo
  template:
    metadata:
      labels:
        app: promptfoo
    spec:
      containers:
        - name: promptfoo
          image: ghcr.io/promptfoo/promptfoo:0.115.2
          ports:
            - containerPort: 3000
          env:
            - name: PROMPTFOO_REMOTE_API_BASE_URL
              value: 'http://localhost:3000'
            - name: PROMPTFOO_REMOTE_APP_BASE_URL
              value: 'http://localhost:3000'
            - name: HTTP_API_KEY  
              value: pass
            - name: PROMPTFOO_CONFIG_DIR
              value: '/app/data'
            - name: PROMPTFOO_DISABLE_TEMPLATE_ENV_VARS
              value: 'false'
            - name: PROMPTFOO_SELF_HOSTED
              value: 'true'
          volumeMounts:
            - name: config
              mountPath: /app/data
      volumes:
        - name: config
          persistentVolumeClaim:  
            claimName: promptfoo
```

Would it be possible to push the 0.115.3 image and be ensure that the binary inside each image matches the tag?

Thank you in advance.

HI @LukashevichVitali, can you pull (0.115.4) instead and let me know if it resolves your issue?https://github.com/promptfoo/promptfoo/pkgs/container/promptfoo/446950309?tag=0.115.4 

I'll look into why 0.115.3 was not published. Thanks for flagging it. 

Hi @mldangelo ,
Great news! I managed to download promptfoo with tag 0.115.4, and it's working flawlessly!

Thank you immensely for your super quick feedback and help. Wishing you and your project huge success!

Any time! Please feel free to comment on this again / file new issues if there are other places where you need help.