4.1.6. Environment Variables
Overview
Environment variables are a cornerstone of Armada’s configuration strategy. They serve as the primary mechanism for passing credentials, service addresses, runtime parameters, and deployment-mode flags to every component in the system. Because Armada runs in two fundamentally different environments — a production Kubernetes cluster and a local development stack — the same set of variables must be delivered through entirely different channels depending on where the code is executing.
In production, sensitive values such as database passwords and API keys are stored as Kubernetes Secrets, which are created once during cluster bootstrap and then referenced by Helm-templated deployment manifests. Non-sensitive configuration (service URLs, platform flags, distribution mode) is written directly into those same Helm templates and rendered at deploy time. Agent pods, which are created dynamically at runtime by the Orchestrator, receive their variables through a programmatically-built Job spec that mixes secret references, hard-coded values, and Kubernetes downward-API fields.
In local development, the same variables originate from a single .env file at the repository root. Docker Compose
loads this file into every container via the env_file directive, while the Orchestrator container additionally receives
inter-service URLs through the environment block in docker-compose.yml. When running outside Docker entirely (the
workbench mode), python-dotenv reads the .env file into os.environ so that Python code can use the same os.getenv()
calls it would use in production.
Understanding which channel delivers each variable — and how that channel differs between environments — is essential for debugging configuration issues, adding new services, or extending the platform with additional secrets.
How Environment Variables Reach Each Component
The diagram below summarizes the injection channels for both deployment modes. Each arrow represents a different mechanism that carries environment variables from their source into a running process.
PRODUCTION (Kubernetes) LOCAL (Container mode / Workbench mode)
┌──────────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────────┐
│ │ │ │
│ .env file │ │ .env file │
│ │ │ │ │ │
│ ▼ │ │ ├──▶ docker-compose env_file: ../.env │
│ bootstrap_secrets.py │ │ │ Loads SQL_SERVER_*, DOCKER_HUB_*, IPQS_KEY │
│ │ │ │ │ into every application container │
│ ├──▶ armada-sql-server-secret (SQL credentials) │ │ │ │
│ ├──▶ armada-docker-registry-secret (image pull auth) │ │ ├──▶ docker-compose environment: block │
│ ├──▶ armada-docker-username-secret (Docker Hub username) │ │ │ Orchestrator only: PLATFORM, RABBITMQ_URL, │
│ └──▶ armada-ipqs-secret (IPQualityScore API key) │ │ │ REDIS_HOST, REDIS_PORT, service URLs │
│ │ │ │ │ Uses Docker DNS names as hostnames │
│ ▼ │ │ │ │
│ Helm templates (deploy/templates/*) │ │ └──▶ python-dotenv (workbench only) │
│ │ │ │ load_env.py reads .env into os.environ │
│ ├──▶ valueFrom.secretKeyRef ─── reads secrets into pod env │ │ Code uses os.getenv() with localhost defaults │
│ ├──▶ value: "..." ─── hardcoded env vars │ │ │
│ └──▶ {{ .Values.* }} ─── Helm values interpolation │ │ Workbench-specific: │
│ │ │ │ uuid.uuid4() ────▶ RUN_ID (generated per run) │
│ ▼ │ │ hardcoded 0 ────▶ POD_INDEX (single-agent mode) │
│ Long-lived Deployments (Orchestrator, Backend, Proxy, etc.) │ │ │
│ │ └──────────────────────────────────────────────────────────────────┘
│ Orchestrator (at runtime, via kubernetes_service.py): │
│ │ │
│ ├──▶ V1EnvVar(value=...) ─── hardcoded values │
│ │ RUN_ID, REDIS_HOST, REDIS_PORT, RABBITMQ_URL, │
│ │ PROXY_PROVIDER_URL, FINGERPRINT_PROVIDER_URL, │
│ │ BACKEND_URL, REQUIREMENTS_TXT │
│ │ │
│ ├──▶ V1EnvVar(valueFrom=secretKeyRef) ── secret references │
│ │ SQL_SERVER_USER, SQL_SERVER_DB, │
│ │ SQL_SERVER_PASSWORD, SQL_SERVER_NAME │
│ │ │
│ └──▶ V1EnvVar(valueFrom=fieldRef) ── downward API │
│ POD_INDEX from job-completion-index annotation │
│ │ │
│ ▼ │
│ Short-lived Agent Pods (Indexed Jobs) │
│ │
└──────────────────────────────────────────────────────────────────┘
Channel details
| Channel | When it runs | What it does | Files involved |
|---|---|---|---|
bootstrap_secrets.py |
Once, during cluster setup | Reads the .env file, builds four Kubernetes Secret objects via the Python K8s client, and applies them (create or update) to the target namespace. |
bootstrap/bootstrap_secrets.py |
| Helm template rendering | At every helm install / helm upgrade |
Templates in deploy/templates/ are rendered with values from deploy/values.yaml and CLI --set overrides. valueFrom.secretKeyRef blocks reference the secrets created above; {{ .Values.* }} expressions inject non-secret config. |
deploy/templates/**/*.yaml, deploy/values.yaml |
| Orchestrator Job builder | Every time a run is launched | kubernetes_service.py constructs a V1Job object in Python, embedding env vars as V1EnvVar entries — some with literal values, some with secretKeyRef, and POD_INDEX via the Kubernetes downward API fieldRef. |
services/orchestrator/app/services/kubernetes_service.py |
Docker Compose env_file |
At docker-compose up |
All application services declare env_file: ../.env, which loads every key=value pair from the root .env into the container environment. |
local/docker-compose.yml, .env |
Docker Compose environment |
At docker-compose up |
The Orchestrator service additionally receives inline env vars for inter-service URLs and platform flags. These use Docker Compose service names as hostnames (resolved by Docker’s built-in DNS). | local/docker-compose.yml |
python-dotenv (workbench mode) |
When running run_workbench.py |
load_env.py calls dotenv.load_dotenv() to inject .env values into os.environ. The workbench then generates a RUN_ID via uuid.uuid4() and sets POD_INDEX=0 manually, since there is no Kubernetes to provide these. |
services/project/workbench/load_env.py, services/project/workbench/run_workbench.py |
Kubernetes Secrets — Creation and Lifecycle
Kubernetes Secrets are the secure storage mechanism for credentials in production. They are not part of the Helm chart — they are created separately by the bootstrap script so that sensitive values never appear in version-controlled Helm values files.
How secrets are created
The script bootstrap/bootstrap_secrets.py performs the following steps:
- Reads the
.envfile from the repository root usingpython-dotenv. - Checks required keys for each secret. If any key is missing, that secret is skipped with a warning.
- Builds
V1Secretobjects using the Kubernetes Python client:armada-sql-server-secret— typeOpaque, containsSQL_SERVER_USER,SQL_SERVER_PASSWORD,SQL_SERVER_DB,SQL_SERVER_NAME.armada-docker-registry-secret— typekubernetes.io/dockerconfigjson, contains a Docker config JSON with Hub credentials and a base64-encoded auth token.armada-docker-username-secret— typeOpaque, contains the Docker Hubusername(used by the Orchestrator to build image references).armada-ipqs-secret— typeOpaque, contains theIPQS_KEYfor IPQualityScore proxy quality checks.
- Applies each secret to the target namespace (default:
default). If the secret already exists, it is replaced; otherwise it is created.
# Typical usage during cluster bootstrap
pip install kubernetes python-dotenv
python bootstrap/bootstrap_secrets.py --namespace default
How secrets are consumed
Helm deployment templates reference these secrets using valueFrom.secretKeyRef:
# Example from deploy/templates/backend/backend-deployment.yaml
env:
- name: SQL_SERVER_USER
valueFrom:
secretKeyRef:
name: armada-sql-server-secret
key: SQL_SERVER_USER
When Kubernetes creates or restarts a pod, the kubelet reads the referenced secret from the API server
and injects its value as a plain-text environment variable inside the container. The application code
reads it with a standard os.getenv("SQL_SERVER_USER") call — it has no knowledge that the value
originated from a Secret.
For agent pods, the Orchestrator’s kubernetes_service.py builds the same secretKeyRef pattern
programmatically using V1EnvVarSource(secret_key_ref=V1SecretKeySelector(...)).
Four secrets at a glance
| Secret name | Type | Keys | Created by | Consumed by |
|---|---|---|---|---|
armada-sql-server-secret |
Opaque |
SQL_SERVER_USER, SQL_SERVER_PASSWORD, SQL_SERVER_DB, SQL_SERVER_NAME |
bootstrap_secrets.py |
Backend, Orchestrator, Proxy Provider, Fingerprint Provider, Agent pods |
armada-docker-registry-secret |
kubernetes.io/dockerconfigjson |
.dockerconfigjson |
bootstrap_secrets.py |
Agent pods (imagePullSecrets) |
armada-docker-username-secret |
Opaque |
username |
bootstrap_secrets.py |
Orchestrator (to build image references in Job specs) |
armada-ipqs-secret |
Opaque |
IPQS_KEY |
bootstrap_secrets.py |
Proxy Provider (optional: true) |
Helm Values Injection
Some environment variables in production originate from Helm values — template parameters that Helm
resolves at deploy time into the final Kubernetes YAML. When a Helm value is placed inside an env:
block, it becomes a container environment variable. But Helm values also control non-env aspects of the
manifest (image references, pull policies, registry secrets) that never reach the process environment.
For the complete reference on all Helm values — including those that do not become environment variables — see the dedicated Helm Values page.
Agent Pod Environment Variables
These are the environment variables injected into every agent pod by the Kubernetes Job spec
built in kubernetes_service.py:
Injected directly by the Orchestrator
| Variable | Source | Value example | Description |
|---|---|---|---|
RUN_ID |
Orchestrator (hardcoded in Job spec) | a1b2c3d4-e5f6-... |
Unique run identifier. Also used as the Celery queue name. |
POD_INDEX |
Kubernetes downward API | 0, 1, 2 |
Derived from metadata.annotations['batch.kubernetes.io/job-completion-index']. Identifies which agent this pod represents. |
REDIS_HOST_VAR_ENV |
Orchestrator (hardcoded) | armada-redis |
Redis hostname for fetching agent config at startup. |
REDIS_PORT_VAR_ENV |
Orchestrator (hardcoded) | 6379 |
Redis port. |
RABBITMQ_URL |
Orchestrator (hardcoded) | amqp://armada-rabbitmq:5672 |
RabbitMQ broker URL for the Celery worker. |
PROXY_PROVIDER_URL |
Orchestrator config | http://armada-proxy-provider:5001 |
URL of the proxy provider service. |
FINGERPRINT_PROVIDER_URL |
Orchestrator config | http://armada-fingerprint-provider:5005 |
URL of the fingerprint provider service. |
BACKEND_URL |
Orchestrator config | http://armada-backend:8000 |
URL of the backend API for monitoring events. |
REQUIREMENTS_TXT |
Orchestrator (base64-encoded) | cmVxdWVzdHM9PTIuMzEuMA== |
Optional. Base64-encoded requirements.txt content. Decoded and installed by entrypoint.sh before the worker starts. |
Injected from Kubernetes Secrets
| Variable | Secret name | Key | Description |
|---|---|---|---|
SQL_SERVER_NAME |
armada-sql-server-secret |
SQL_SERVER_NAME |
SQL Server hostname |
SQL_SERVER_DB |
armada-sql-server-secret |
SQL_SERVER_DB |
Database name |
SQL_SERVER_USER |
armada-sql-server-secret |
SQL_SERVER_USER |
Database username |
SQL_SERVER_PASSWORD |
armada-sql-server-secret |
SQL_SERVER_PASSWORD |
Database password |
Defaults (used when variable is not set)
| Variable | Default | Used by |
|---|---|---|
RABBITMQ_URL |
amqp://localhost:5672 |
services/agent/main.py |
REDIS_HOST_VAR_ENV |
localhost |
services/agent/src/load_agent_message.py |
REDIS_PORT_VAR_ENV |
6379 |
services/agent/src/load_agent_message.py |
BACKEND_URL |
http://localhost:8000 |
services/agent/src/monitoring_client.py |
PROXY_PROVIDER_URL |
http://127.0.0.1:5001 |
services/agent/src/proxy_manager.py |
FINGERPRINT_PROVIDER_URL |
http://localhost:5005 |
services/agent/src/fingerprint_manager.py |
POD_INDEX |
0 |
services/agent/src/load_agent_message.py |
Orchestrator Environment Variables
| Variable | Source | Description |
|---|---|---|
PLATFORM |
Helm values / docker-compose | "distant" (production) or "local" (Docker Compose). Controls whether the orchestrator creates Kubernetes Jobs. |
DISTRIB |
Helm values | "kube" (production) or "minikube". Controls imagePullPolicy (Always vs Never) and registry secrets. |
REDIS_HOST |
Helm values / docker-compose | Redis hostname for pushing agent configs. |
REDIS_PORT |
Helm values / docker-compose | Redis port. |
RABBITMQ_URL |
Helm values / docker-compose | RabbitMQ URL for dispatching job messages. |
PROXY_PROVIDER_URL |
Helm values / docker-compose | Forwarded to agent pods as env var. |
FINGERPRINT_PROVIDER_URL |
Helm values / docker-compose | Forwarded to agent pods as env var. |
BACKEND_URL |
Helm values / docker-compose | Forwarded to agent pods as env var. |
DOCKER_HUB_USERNAME |
Kubernetes Secret | Docker Hub username for image references in the Kubernetes Job spec. |
Backend API Environment Variables
| Variable | Source | Description |
|---|---|---|
SQL_SERVER_NAME |
.env / Kubernetes Secret |
SQL Server hostname |
SQL_SERVER_DB |
.env / Kubernetes Secret |
Database name |
SQL_SERVER_USER |
.env / Kubernetes Secret |
Database username |
SQL_SERVER_PASSWORD |
.env / Kubernetes Secret |
Database password |
Proxy Provider Environment Variables
| Variable | Source | Description |
|---|---|---|
SQL_SERVER_NAME |
.env / Kubernetes Secret |
SQL Server hostname |
SQL_SERVER_DB |
.env / Kubernetes Secret |
Database name |
SQL_SERVER_USER |
.env / Kubernetes Secret |
Database username |
SQL_SERVER_PASSWORD |
.env / Kubernetes Secret |
Database password |
IPQS_KEY |
.env / Kubernetes Secret |
IPQualityScore API key for proxy quality checks |
Fingerprint Provider Environment Variables
| Variable | Source | Description |
|---|---|---|
SQL_SERVER_NAME |
.env / Kubernetes Secret |
SQL Server hostname |
SQL_SERVER_DB |
.env / Kubernetes Secret |
Database name |
SQL_SERVER_USER |
.env / Kubernetes Secret |
Database username |
SQL_SERVER_PASSWORD |
.env / Kubernetes Secret |
Database password |
Local Development — Container mode (Docker Compose)
In local/docker-compose.yml, environment variables are set through two mechanisms:
1. env_file directive
All application services use env_file: ../.env to load the root .env file. This provides SQL Server credentials and API keys.
2. environment directive (Orchestrator only)
The orchestrator gets additional variables for inter-service communication:
armada-orchestrator:
environment:
PLATFORM: "local"
RABBITMQ_URL: "amqp://armada-rabbitmq:5672"
REDIS_HOST: "armada-redis"
REDIS_PORT: "6379"
PROXY_PROVIDER_URL: "http://armada-proxy-provider:5001"
FINGERPRINT_PROVIDER_URL: "http://armada-fingerprint-provider:5005"
BACKEND_URL: "http://armada-backend:8000"
These use Docker Compose service names as hostnames, which Docker’s internal DNS resolves to the correct container.
Local Development — Workbench mode
The workbench (services/project/workbench/run_workbench.py) loads environment variables from the .env file via python-dotenv:
from load_env import * # loads .env into os.environ
Since the workbench runs outside Docker, it uses localhost addresses (the defaults) to reach services started by Docker Compose with exposed ports. The workbench also generates values that would normally come from Kubernetes:
RUN_ID— generated asuuid.uuid4()instead of being passed by the Orchestrator.POD_INDEX— hardcoded to0since the workbench always simulates a single agent.