Configure environment
Every service ships a vars.yaml that declares every environment variable it needs at runtime, with where the value should come from. Step 2 is to make sure each declared variable has a value waiting for it in either:
- the bootstrap
.envthat cloud-init drops at/opt/deployment/.env(variables flaggedsource: local), or - AWS SSM Parameter Store under
/${NAMESPACE}/${SERVICE}/<VAR>(non-sensitive), or - AWS Secrets Manager under
${NAMESPACE}/${SERVICE}/secrets(or any name-prefixed secret) for sensitive values, or - the manifest's
default:field (when nothing else provides it), or - another secret referenced by
secret.arn_from(typically a managed-database master secret — see Managed database secrets).
If any required: true variable is missing from every source, fetch-env.sh aborts and init.sh never starts the containers. That is the safety net for the whole bring-up.
For a deep dive on the manifest schema and resolution order, see:
Two ways to seed
You only need one of these per environment.
Option A — Seed from environment files (recommended)
The deployment bundle ships a seed-env.sh script that reads .env files and fans out values into SSM and Secrets Manager based on each service's vars.yaml (sensitive entries land in SM, the rest in SSM).
# Preview
./scripts/seed-env.sh \
--namespace voiceai/staging \
--env-dir /tmp/seed/ \
--dry-run
# Apply
./scripts/seed-env.sh \
--namespace voiceai/staging \
--env-dir /tmp/seed/
# Or one service at a time:
./scripts/seed-env.sh \
--namespace voiceai/staging \
--service api \
--env-file /tmp/api.env
The script's --show-required flag prints every variable each service needs (or just one service with --service api --show-required), which is the easiest way to verify completeness before moving on.
Option B — Create SSM / Secrets Manager entries yourself
If you do not use the seed script, create the same shape manually or with your own IaC:
- one SSM parameter per non-sensitive declared variable, and
- one Secrets Manager secret per service named
${NAMESPACE}/${SERVICE}/secretscontaining a JSON blob with every sensitive variable as a key.
For example:
/${NAMESPACE}/api/REDIS_HOST # SSM
${NAMESPACE}/api/secrets -> {"DATABASE_URL":"...", "REDIS_PASSWORD":"..."} # SM
After the shape exists, populate real values in the AWS Console, CLI, or your secret-management workflow.
Variables flagged with secret.arn_from (e.g. DATABASE_PASSWORD resolved from an RDS master secret) and variables with compose.template (e.g. DATABASE_URL built at runtime) are not created as normal SSM/SM entries — they live elsewhere or are computed.
What to populate per service
The minimum unconditionally required variables per service are summarized below. All of these are required: true in the service's vars.yaml with no default and source other than local — i.e. you must put them in SSM / SM (or rely on a secret.arn_from for them).
| Service | SSM (non-sensitive) | Secrets Manager (sensitive) |
|---|---|---|
database | — | POSTGRES_PASSWORD, REDIS_PASSWORD |
api | REDIS_HOST, DOMAIN_TELPRO | DATABASE_URL, REDIS_PASSWORD, JWT_SECRET |
web | REDIS_HOST, NEXTAUTH_URL, DOMAIN_TELWEB | DATABASE_URL, REDIS_PASSWORD, NEXTAUTH_SECRET |
voice | REDIS_HOST, VAIPROXIESPIPED, SIP_PORT_INTERNAL, SIPPORTTELPRO | DATABASE_URL, REDIS_PASSWORD |
telpro | REDIS_HOST, DOMAIN_TELPRO | REDIS_PASSWORD |
ops | REDIS_HOST | DATABASE_URL, REDIS_PASSWORD |
media | — | — (MEDIA_UPLOAD_TOKEN and MEDIA_TLS_* only when used) |
squid | — | — |
Many values are shared across services (REDIS_HOST, REDIS_PASSWORD, DATABASE_URL, DOMAIN_TELPRO). Store them as per-service copies so an operator can rotate one service's view of a value without touching another. Drift between copies is a common bug; if a service can't authenticate to Redis after a rotation, check that REDIS_PASSWORD is the same string in voiceai/<env>/api/secrets, voiceai/<env>/voice/secrets, etc.
For per-service variable details, see the Configuration tab on each service's operations page.
Verifying the seed
Before you provision instances, verify every service can resolve its full env from AWS.
fetch-env.sh accepts --format export and writes export VAR=value lines to stdout (no file). You can run it on any host that has the AWS CLI and IAM grants:
# On any workstation or jumpbox with AWS access:
NAMESPACE=voiceai/staging \
AWS_REGION=eu-central-1 \
/opt/services/common/fetch-env.sh \
--manifest /opt/services/api/vars.yaml \
--bootstrap /opt/deployment/.env \
--format export | wc -l
A successful run logs Exported N variables to process environment. A missing required variable prints FATAL: Required variables missing from SSM/SM/bootstrap: and the list — fix in AWS Console / CLI and re-run.
Sensitive vs non-sensitive — the rule of thumb
When in doubt, assume:
- Anything that looks like a credential, key, or PEM → Secrets Manager (
sensitive: trueinvars.yaml). - Anything an operator might need to reason about in plain text (host names, ports, feature toggles, log levels) → SSM Parameter Store.
fetch-env.sh reads SSM with --with-decryption so SecureString parameters work too, but the convention is: real secrets in SM, everything else in SSM. The whole vars.yaml schema and a worked secret.arn_from example for managed databases live in Configuration.
Next
Continue to Provision instances.