Secrets
Patterns for keeping database passwords and API keys out of faz.yaml until env-var interpolation lands.
faz.yaml declares database connection strings, including passwords. Putting credentials directly in a YAML file is something nobody is happy about. This page covers the current state and the working patterns.
The current state
The faz init template shows ${POSTGRES_PASSWORD} syntax in the password: field, which suggests environment-variable interpolation. It is not yet implemented. The literal string ${POSTGRES_PASSWORD} is what gets passed to the database driver, and the connection fails.
Until interpolation ships, you have three working patterns. Pick the one that fits your environment.
Pattern 1 — Pre-set environment + plain literal at runtime
The simplest pattern. Set the literal value of password: in faz.yaml from a secret stored outside the file, then commit only the rendered file to a private location (or never commit it at all).
For development:
# .env or your shell rc
export POSTGRES_PASSWORD=dev-only-not-realThen write the literal value into faz.yaml:
databases:
- name: <database>
type: postgresql
host: localhost
username: <username>
password: <literal-password> # literal value — interpolation isn't implemented yetAdd faz.yaml to .gitignore. Keep an example committed under a different name (faz.example.yaml) with placeholders for documentation.
This is fine for local dev. It does not scale to production.
Pattern 2 — Render faz.yaml at deploy time
Keep a template file in version control with placeholders, then render it into faz.yaml during deployment using the secrets your platform exposes via environment variables.
faz.template.yaml (committed):
databases:
- name: <database>
type: postgresql
host: ${DB_HOST}
port: 5432
database: ${DB_NAME}
username: ${DB_USER}
password: ${DB_PASSWORD}Render at deploy time with envsubst:
envsubst < faz.template.yaml > faz.yamlOr with a more powerful tool like gomplate or ytt if you need conditionals:
gomplate -f faz.template.yaml -o faz.yamlThe rendered faz.yaml lives only on the runtime filesystem; secrets don't pass through git. Most CI/CD systems (GitHub Actions, GitLab CI, etc.) inject secrets as environment variables at job start, so envsubst works without extra setup.
With Docker
FROM python:3.12-slim
RUN apt-get update && apt-get install -y --no-install-recommends gettext-base \
&& rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir faz-core
COPY faz.template.yaml /app/faz.template.yaml
WORKDIR /app
ENTRYPOINT ["sh", "-c", "envsubst < faz.template.yaml > faz.yaml && exec faz serve --host 0.0.0.0"]The container reads secrets from its environment (which Docker, Compose, or Kubernetes populates from a secret store), renders the file at boot, and starts faz.
Pattern 3 — Mount secrets, render in a wrapper
If your platform mounts secrets as files (Docker secrets, Kubernetes Secrets, HashiCorp Vault Agent), wrap faz serve in a small entrypoint that reads each file and exports it.
#!/bin/sh
export DB_PASSWORD="$(cat /run/secrets/postgres_password)"
export PINECONE_API_KEY="$(cat /run/secrets/pinecone_api_key)"
envsubst < /etc/faz/faz.template.yaml > /etc/faz/faz.yaml
exec faz serve --host 0.0.0.0For Kubernetes specifically, mount the Secret as a volume and use the same envsubst pattern. Don't put secrets in environment variables on Pods if your security policy forbids it — the file-mount + read-into-env wrapper above keeps them off process listings.
Pinecone, DynamoDB, and other API-key connectors
Some connectors use the extra: mapping for API keys rather than password:. The same patterns apply:
databases:
- name: <database>
type: pinecone
host: <index-hostname> # e.g. my-index-xxxx.svc.us-east-1-aws.pinecone.io
extra:
api_key: ${PINECONE_API_KEY} # rendered by envsubst at deploy timeFor DynamoDB on AWS, the standard answer is don't put credentials in faz.yaml at all. Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as environment variables, or run faz on an EC2/ECS/EKS instance with an IAM role attached. The boto3 client picks credentials up automatically:
databases:
- name: <database>
type: dynamodb
host: dynamodb.<region>.amazonaws.com
port: 443
database: <table>
extra:
region: <region> # e.g. us-east-1
# username and password are intentionally absent — AWS SDK reads
# AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY from the environment,
# or from the IAM role attached to the host.What not to do
- Don't commit a real
faz.yamlto a public repository. It contains the connection details for everything faz can read. - Don't store secrets in shell history. If you
export FOO=barin your shell, that string is in~/.bash_historyor~/.zsh_history. Useread -sfor interactive entry, or pull from a password manager. - Don't build secrets into a container image. Mount them at runtime instead. Anything in the image layer is recoverable by anyone with image access.
- Don't share
faz.yamlbetween dev and prod. Render separate files per environment; the connection details andpermissions:will diverge.
Roadmap
${VAR} interpolation in faz.yaml is a known gap and is on the roadmap. Track the source for movement.
When it lands, the simplest pattern will become:
databases:
- name: <database>
password: ${POSTGRES_PASSWORD} # interpolated from environment at load time…with faz reading the value from the environment of the process. Until then, render the file before faz reads it.
Related
faz.yaml— the schema reference, with thepassword:andextra:fields documented.- Connection failed — common auth-related connection errors.