# How to Use Variable Substitution in Rudder CLI


> Keep credentials out of Git by resolving YAML variable references from a var file when you apply Rudder CLI resources.



**Announcement**

**Variable substitution requires Rudder CLI v0.25.0 or later**.

Upgrade if you are using an earlier version.

Keep destination and warehouse credentials out of the YAML you commit. Write `{{ .VAR }}` references in specs, store the values in a var file, and pass `--var-file` when you apply.

You can also substitute non-secret values (for example an `account_id` that differs across development and production).

## Prerequisites

- [Install Rudder CLI](https://www.rudderstack.com/docs/rudder-cli/installation/) and [authenticate](https://www.rudderstack.com/docs/rudder-cli/#authenticate-the-cli) against your workspace.
- Define or import YAML from the [Destination YAML Reference](https://www.rudderstack.com/docs/rudder-cli/yaml-destinations/) or [Account Resources YAML Reference](https://www.rudderstack.com/docs/rudder-cli/yaml-accounts/) that uses secret fields.

## Write variable references in YAML

Write each substituted field as `{{ .VARIABLE_NAME }}`. The name must match a key in the var file.

Use a variable for every **secret** field. Don't put the credential in the spec as a literal.

| Resource | Example secret keys | YAML reference |
| :--- | :--- | :--- |
| Amazon S3 destination (`role_based_auth: false`) | `access_key_id`, `access_key` | `{{ .AWS_ACCESS_KEY_ID }}`, `{{ .AWS_SECRET_ACCESS_KEY }}` |
| BigQuery account | `credentials` | `{{ .BQ_CREDENTIALS }}` |
| PostgreSQL account | `password` | `{{ .PG_PASSWORD }}` |
| Snowflake account | `privateKey`, `privateKeyPassphrase`, or `password` | `{{ .SF_PRIVATE_KEY }}`, `{{ .SF_PRIVATE_KEY_PASSPHRASE }}`, or `{{ .SF_PASSWORD }}` |

See [Destination Type Reference for Rudder CLI](https://www.rudderstack.com/docs/rudder-cli/destination-types/) for the secret keys of each destination type, and the [Account Resources YAML Reference](https://www.rudderstack.com/docs/rudder-cli/yaml-accounts/) for account keys.

### Write Amazon S3 access keys

```yaml
config:
  bucket_name: "my-rudder-bucket"
  role_based_auth: false
  access_key_id: "{{ .AWS_ACCESS_KEY_ID }}"
  access_key: "{{ .AWS_SECRET_ACCESS_KEY }}"
```

### Write BigQuery account credentials

```yaml
config:
  project: "acme-analytics-prod"
  location: "US"
  credentials: '{{ .BQ_CREDENTIALS }}'
```

## Create a var file

Create `credentials.vars.yaml` with keys that match the names in your specs:

```yaml
AWS_ACCESS_KEY_ID: "AKIA..."
AWS_SECRET_ACCESS_KEY: "wJal..."
BQ_CREDENTIALS: '{"type":"service_account","project_id":"acme-analytics-prod","private_key":"..."}'
```

Store the file next to the project, or at any path you can pass to `--var-file`. It holds real credentials.

## Apply with a var file

Pass `--var-file` on apply so Rudder CLI resolves the references before it sends the payload:

```bash
rudder-cli apply --location ./project --var-file ./credentials.vars.yaml
```

You can combine `--var-file` with `--dry-run` to preview the workspace changes first.

**Warning:** **Secrets are never read back.** The RudderStack API doesn&#39;t return credential values, so Rudder CLI can&#39;t compare the remote secret against your local one. It re-sends the secret on every apply. A re-apply that reports a change to a secret field is expected, not drift.

## Keep the var file out of version control

Add the var file to `.gitignore`. Commit the specs that contain `{{ .VAR }}` references; keep the resolved values local or in your CI secret store. In CI, write the var file at job time and pass `--var-file` on apply.

## Fill placeholders after import

`rudder-cli import workspace` writes the secret fields it receives as `{{ .VAR }}` placeholders rather than values. For destinations, the imported YAML may or may not include every secret key, so before you apply, make sure each secret key the destination needs is present and populated through variable substitution.

Import also scaffolds `secrets.vars.yaml` under `imported/` — one placeholder line per variable the generated specs reference. Fill each placeholder with the real credential, then apply with `--var-file`. An existing `secrets.vars.yaml` is never overwritten, so values you've already filled in survive a re-import.

**Tip**

An unfilled (null) placeholder makes the apply fail rather than silently sending an empty credential. To send an empty value on purpose, set the key to `""`.

**Warning:** Once your project holds `{{ .VAR }}` references, pass `--var-file` to `import workspace` as well as to `apply`. Import loads and diffs the project before writing, so an unresolved reference reads as drift and fails the command. See [Import a project that uses variable substitution](https://www.rudderstack.com/docs/rudder-cli/import-resources/#import-with-var-file).

See [How to Import Workspace Resources into Your Rudder CLI Project](https://www.rudderstack.com/docs/rudder-cli/import-resources/) for the import workflow.

## See more

- [Destination YAML Reference](https://www.rudderstack.com/docs/rudder-cli/yaml-destinations/) for S3 secret keys
- [Account Resources YAML Reference](https://www.rudderstack.com/docs/rudder-cli/yaml-accounts/) for warehouse credential keys
- [End-to-End Walkthrough: Destinations with Rudder CLI](https://www.rudderstack.com/docs/rudder-cli/destinations-walkthrough/)
- [Core Rudder CLI Commands Reference](https://www.rudderstack.com/docs/rudder-cli/commands/)

