Skip to content
TulipFarmDocs

Last updated

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.

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.

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 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.

Each method below supplies the operator settings. Generated settings stay in the generated volume. To keep them in your store too, see Generated settings in a store.

Terminal window
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.

Store the names from .env.example in a Doppler config. Then run Compose through the Doppler CLI:

Terminal window
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 that can read only this config.

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

Store the names in an Infisical environment. Then use the Infisical CLI:

Terminal window
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.

Replace each value in a copy of .env.example with a secret reference. This file holds no secrets, so you can commit it:

Terminal window
# tulipfarm.op.env
POSTGRES_PASSWORD=op://TulipFarm/postgres/password
ANTHROPIC_API_KEY=op://TulipFarm/anthropic/credential
Terminal window
op run --env-file tulipfarm.op.env -- docker compose up -d --build

Vault Agent renders a template to an env file. Point Compose at it:

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
}
Terminal window
docker compose --env-file /etc/tulipfarm/operator.env up -d --build

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, or export values before you run Compose:

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

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 syncs that Secret from any supported store, such as AWS, GCP, Azure, Vault, Doppler, Infisical, or 1Password:

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) once as a Job against your database and object storage. Then load each generated file into the store, as described in the next section.

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:

Terminal window
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.

  • 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.

Was this page helpful?