# Supply settings from a secret store

> Deliver TulipFarm settings as environment variables from Doppler, Infisical, 1Password, Vault, a cloud secret manager, Kubernetes, or a file.

Source: https://tulipfarm.ai/docs/self-hosting/secrets/

TulipFarm does not include a client for any secret store. The application reads
environment variables. Your delivery layer fetches values from the store and sets
those variables. To change stores, change only the delivery layer.

```text
 secret store ──▶ delivery layer ──────────────────────▶ environment ──▶ TulipFarm
 Doppler          doppler run -- docker compose up         POSTGRES_PASSWORD=…
 Infisical        infisical run -- docker compose up       ANTHROPIC_API_KEY=…
 1Password        op run --env-file … -- docker compose up
 SOPS + KMS       sops exec-env … 'docker compose up'
 Kubernetes       External Secrets Operator → Secret → envFrom
 a file           self-host/.env (chmod 600)
```

Docker Compose reads [interpolation variables](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/)
from the shell environment first, then from `self-host/.env`. So any command that
runs `docker compose` with variables already set works without changes. Use the
names in [`self-host/.env.example`](https://github.com/TulipFarm/tulipfarm/blob/main/self-host/.env.example).

Each method below supplies the [operator settings](https://tulipfarm.ai/docs/self-hosting/configuration/index.md#operator-settings).
Generated settings stay in the `generated` volume. To keep them in your store too,
see [Generated settings in a store](#generated-settings-in-a-store).

## A local file

```sh
cp .env.example .env
chmod 600 .env
```

Put single quotes around values that contain `$`. Never commit `.env`. To keep the
file encrypted at rest, use [SOPS with your KMS](https://tulipfarm.ai/docs/self-hosting/kms/index.md).

## Doppler

Store the names from `.env.example` in a Doppler config. Then run Compose through the
[Doppler CLI](https://docs.doppler.com/docs/cli):

```sh
doppler setup --project tulipfarm --config prd
doppler run -- docker compose up -d --build
```

On a server without interactive login, set `DOPPLER_TOKEN` to a
[service token](https://docs.doppler.com/docs/service-tokens) that can read only this config.

To import an existing file: `doppler secrets upload .env`.

## Infisical

Store the names in an Infisical environment. Then use the
[Infisical CLI](https://infisical.com/docs/cli/commands/run):

```sh
infisical run --env=prod --expand=false -- docker compose up -d --build
```

`--expand=false` stops Infisical from expanding `${…}` references inside values.
To import an existing file: `infisical secrets set --file .env --env=prod`. On a
server, authenticate with a [machine identity](https://infisical.com/docs/documentation/platform/identities/machine-identities).

## 1Password

Replace each value in a copy of `.env.example` with a
[secret reference](https://developer.1password.com/docs/cli/secret-references/).
This file holds no secrets, so you can commit it:

```sh
# tulipfarm.op.env
POSTGRES_PASSWORD=op://TulipFarm/postgres/password
ANTHROPIC_API_KEY=op://TulipFarm/anthropic/credential
```

```sh
op run --env-file tulipfarm.op.env -- docker compose up -d --build
```

## HashiCorp Vault and OpenBao

[Vault Agent](https://developer.hashicorp.com/vault/docs/agent-and-proxy/agent/template)
renders a template to an env file. Point Compose at it:

```hcl
template {
  destination = "/etc/tulipfarm/operator.env"
  perms       = "0600"
  contents    = <<-EOT
  {{ with secret "kv/data/tulipfarm" }}{{ range $k, $v := .Data.data }}{{ $k }}={{ $v }}
  {{ end }}{{ end }}
  EOT
}
```

```sh
docker compose --env-file /etc/tulipfarm/operator.env up -d --build
```

## Cloud secret managers

For AWS Secrets Manager, Google Secret Manager, or Azure Key Vault secrets, use the
platform's runtime integration. Examples: ECS task `secrets`, Cloud Run
`--set-secrets`, or Container Apps `secretref`. Each one sets the same environment
variable names. On a single Compose host, either use [SOPS with that cloud's KMS](https://tulipfarm.ai/docs/self-hosting/kms/index.md),
or export values before you run Compose:

```sh
export ANTHROPIC_API_KEY="$(aws secretsmanager get-secret-value \
  --secret-id tulipfarm/anthropic --query SecretString --output text)"
```

## Kubernetes

TulipFarm does not ship a Helm chart yet. Run the same images, and give each Deployment
its variables from a Kubernetes Secret. The
[External Secrets Operator](https://external-secrets.io/latest/) syncs that Secret
from any supported store, such as AWS, GCP, Azure, Vault, Doppler, Infisical, or 1Password:

```yaml
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata: { name: tulipfarm-api }
spec:
  refreshInterval: 1h
  secretStoreRef: { kind: ClusterSecretStore, name: your-store }
  target: { name: tulipfarm-api }
  dataFrom:
    - extract: { key: tulipfarm/api }
---
# In the api Deployment's container spec:
envFrom:
  - secretRef: { name: tulipfarm-api }
```

Run the setup image ([`apps/database/Dockerfile`](https://github.com/TulipFarm/tulipfarm/blob/main/apps/database/Dockerfile)) once as a Job against your database
and object storage. Then load each generated file into the store, as described in the
next section.

## Generated settings in a store

`pnpm self-host:setup --out DIR` writes one env file per service. To manage them like
other secrets, upload each file, then delete the local copy:

```sh
doppler secrets upload --project tulipfarm --config prd_api generated/api.env
infisical secrets set --file generated/worker.env --env=prod --path=/worker
```

Keep `state.json` as an encrypted backup. Setup needs it to rerun without changing
passwords or keys. See [KMS: encrypt backups](https://tulipfarm.ai/docs/self-hosting/kms/index.md#encrypt-backups).

## Rules for every store

- Give each service only its own variables. The worker does not need `BETTER_AUTH_SECRET`.
- Use read-only, machine-scoped credentials on servers.
- Restart the services after you change a value. TulipFarm reads settings at startup.
- Never pass secrets as command-line arguments. Process lists and shell history can expose them.
- To keep secrets out of the environment, mount each one as a file and set `<NAME>_FILE`
  to its path. See [Secret files](https://tulipfarm.ai/docs/self-hosting/configuration/index.md#secret-files).
