faz
Configuration

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

Then 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 yet

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

Or with a more powerful tool like gomplate or ytt if you need conditionals:

gomplate -f faz.template.yaml -o faz.yaml

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

entrypoint.sh
#!/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.0

For 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 time

For 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.yaml to a public repository. It contains the connection details for everything faz can read.
  • Don't store secrets in shell history. If you export FOO=bar in your shell, that string is in ~/.bash_history or ~/.zsh_history. Use read -s for 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.yaml between dev and prod. Render separate files per environment; the connection details and permissions: 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.

  • faz.yaml — the schema reference, with the password: and extra: fields documented.
  • Connection failed — common auth-related connection errors.

On this page