Skip to content

Configuration and environment variables

Consolidated index of environment variables across the four platform repos, with the cross-repo contracts called out first — those are the settings where a mismatch between two repos causes a silent or confusing production failure.

Names and defaults only

This page documents variable names, defaults, and which repos must agree. Never record secret values here. Real values live in .env files (gitignored), AWS Secrets Manager, or the ECS task role.

Per-repo templates are the authoritative source:

Repo Template
verilib-frontend .env.example, docker/compose.env, docker/.env.stack
verilib-atomizer deploy/config/example.env
local_validate .env.example
verilib-cli .verilib/config.json — see CLI config and files

Cross-repo contracts

These must match across repos. Each row is a real failure mode, not a style preference.

Contract Frontend Atomizer Cert worker What breaks on mismatch
RabbitMQ vhost RABBITMQ_VHOST (default verilib) RABBITMQ_VHOST RABBITMQ_VHOST (required, no default) Peers connect successfully to different vhosts and silently exchange no messages. The cert worker deliberately refuses to start without an explicit value rather than fall back.
Cert manifest bucket S3_CERT_BUCKET S3_BUCKET The frontend health-checks its bucket at startup and cannot fetch manifests the worker wrote elsewhere. Note the different variable names for the same bucket.
Repo tree bucket (upload target) S3_BUCKET S3_REPOS_BUCKET The cert worker downloads the parent repo tree from where the atomizer wrote it; a mismatch means "tree not found" on every certify.
GitHub token encryption JWT_KEY GITHUB_TOKEN_ENCRYPTION_KEY or JWT_KEY The upload processor cannot decrypt the ciphertext the frontend put on the message; every private-repo clone fails.
Queue topology arguments BaseQueue.php broker/topology.py broker/topology.py RabbitMQ 406 PRECONDITION_FAILED closes the channel of whichever peer declares second.
Queue / exchange names Queue classes <PREFIX>_EXCHANGE / _QUEUE / _ROUTING_KEY same Messages published to an exchange nobody consumes. Override only in lockstep.
Probe schema version Schema 3.0 parsers Schema 3.0 manifest Parse failures after a probe upgrade — see Probe ecosystem.

PROOF_LANGUAGE_ID_MAP is environment-specific

proof_language_id on an atomize.request selects the probe. The mapping is configuration, not a stable contract, and the two tracked values differ:

Source Value
Code default / docs/protocols/atomize.md 1:Rust,2:Lean,3:Aeneas
deploy/config/example.env (deployment) 10:Rust,2:Lean,11:Aeneas

Read the deployed value before assuming 1/2/3. The ids correspond to rows in the frontend's languages table, so they diverge whenever that table is seeded differently.

RabbitMQ

Shared by the atomizer and cert workers (broker/config.py in both repos).

Variable Default Notes
RABBITMQ_HOST (required)
RABBITMQ_PORT 5672
RABBITMQ_VHOST required in local_validate; / in atomizer code; verilib in frontend and deploy example See the contract table above.
RABBITMQ_USER / RABBITMQ_PASSWORD guest / guest In production these come from AWS Secrets Manager via RABBITMQ_SECRET_ARN, never from a file.
RABBITMQ_TLS 0
RABBITMQ_PREFETCH 1 Kept at 1 so a slow message can't starve sibling workers.
RABBITMQ_HEARTBEAT 60
RABBITMQ_CLIENT_NAME per repo e.g. verilib-local-validate.
RABBITMQ_MGMT_PORT 15672 Management API, polled by the queue-depth Lambda.

Queue name overrides follow <PREFIX>_EXCHANGE / _QUEUE / _ROUTING_KEY for each of UPLOAD_REQUEST, UPLOAD_RESPONSE, ATOMIZE_REQUEST, ATOMIZE_RESPONSE, LIVELOG, VALIDATE_REQUEST, VALIDATE_RESPONSE, VALIDATE_LIVELOG, PROMOTE_REQUEST, PROMOTE_RESPONSE. Full inventory: Message contracts.

S3

Variable Repos Default Notes
S3_BUCKET atomizer, cert worker (required) Different meaning per repo: repo trees in the atomizer, cert manifests in the worker.
S3_REPOS_BUCKET cert worker verilib-repos Where the atomizer's uploaded trees live.
S3_CERT_BUCKET frontend Must equal the cert worker's S3_BUCKET.
AWS_REGION all us-east-1 The deploy example uses eu-north-1.
S3_ENDPOINT_URL all blank = real AWS Set for MinIO / S3-compatible backends.
S3_MAX_CONCURRENCY cert worker 8
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY local dev only Production uses the ECS task IAM role; no keys are set.

Key layouts: repo trees at s3://<bucket>/<repo_id>/, cert artifacts at s3://<bucket>/cert/<repo_id>/<filename>.

Database (frontend and legacy atomizer paths only)

Variable Notes
DB_HOST MySQL host. Amazon RDS endpoint in production; the Compose service name (mysql) in local development. See Managed.
DB_PORT 3306. Define it once — see the warning below.
DB_NAME, DB_USER verilib in both RDS and the Compose stack.
DB_PASSWORD The RDS master password for that environment; the Compose default locally. Read from the host's gitignored .env — see Managed → Credentials.
DB_TYPE Frontend; e.g. eloquent.

A duplicate DB_PORT breaks auth while pages still work

The frontend's two connection sites disagree about the port: public/core/Database.php builds the PDO DSN without a port (driver default 3306), while public/api/user/User.php passes DB_PORT to mysqli_connect. A leftover second DB_PORT= line later in .env wins, so PDO queries keep reaching the database while the auth API connects to the wrong port — which presents as a failed login or an unrendered page rather than a connection error. Keep exactly one DB_PORT= line — 3306, unless a local stack has been remapped to publish MySQL elsewhere.

Dotenv is immutable — restart PHP after changing DB_*

public/bootstrap.php loads the env file in immutable mode, so a variable already in the PHP process environment overrides the file. Reload PHP-FPM and Apache, and restart the DB-writing workers (upload_response, atomize_response, validate_response, livelog, validate_livelog), or the old connection settings stay live.

Compose-only variables

These configure the containers themselves, not the PHP application, so they live in the file passed as --env-file rather than in the app's .env. The full local stack reads docker/.env.stack (committed defaults):

Variable Notes
MYSQL_VERSION Tag for the mysql image — keep it on 8.0 to match the schema dump.
MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD, MYSQL_ROOT_PASSWORD Consumed by the MySQL image on first start to create the database and user. They seed the container; the app still reads DB_NAME / DB_USER / DB_PASSWORD, so the two sets must agree. Changing them after the volume exists has no effect until the volume is recreated.
APACHE_VERSION Tag for the Apache image.
PROJECT_ROOT Host path mounted as the document root.

The older docker-compose.yml path uses .env.docker.dev, which spells the same MySQL values DB_NAME / DB_USERNAME / DB_PASSWORD / DB_ROOT_PASSWORD plus COMPOSE_PROJECT_NAME. Note DB_USERNAME there is the container-seeding name, distinct from the application's DB_USER.

The cert workers have no DB_* variables at all — that is a deliberate architectural property, not an omission. See Worker.

Probes

Variable Default Notes
ATOMIZER_LANGUAGES Rust,Lean,Aeneas Which plugins to enable. Deploy example uses Lean,Aeneas,Rust.
PROOF_LANGUAGE_ID_MAP see above
PROBE_VERUS_BIN ~/.cargo/bin/probe-verus Native binary (local mode).
PROBE_LEAN_BIN ~/.elan/bin/probe-lean
PROBE_AENEAS_BIN ~/.cargo/bin/probe-aeneas
PROBE_VERUS_DOCKER_IMAGE probe-verus:latest Docker image — preferred over the native binary when set.
PROBE_LEAN_DOCKER_IMAGE probe-lean:latest
PROBE_AENEAS_DOCKER_IMAGE probe-aeneas:latest
PROBE_VERUS_VERSION / PROBE_AENEAS_VERSION / PROBE_LEAN_VERSION e.g. 6.10.1 / 0.11.0 / latest Image tags built by build_and_push.sh.
PROBE_OUTPUT_DIR ./probe-out Cert worker; manifest read from <dir>/cert<repo_id>/probe-manifest-complete.json.
PROBE_DOCKER_* (+ _LEAN / _AENEAS) Cert worker build/run/push controls.
WORKER_REQUIRE_DOCKER Entrypoint check; the atomize processor needs a host Docker socket.
LEAN_REPO_LANGUAGE_ID, AENEAS_REPO_LANGUAGE_ID Cert worker probe-kind resolution.

Runtime and retries

Variable Default Notes
CLI_TIMEOUT 4800 Probe subprocess timeout in seconds. Must stay ≤ the broker's consumer_timeout or RabbitMQ kills the channel mid-probe.
UPLOAD_MAX_RETRIES 5
ATOMIZE_MAX_RETRIES 5 (deploy example: 3)
JOB_MAX_ATTEMPTS 5 Cert workers.
UPLOAD_WORKDIR / ATOMIZE_WORKDIR system tmp Ephemeral scratch; see Disk cleanup.
WORKDIR_ROOT /var/verilib/work Host path bind-mounted into the ECS task.
POLL_INTERVAL, MAX_WORKERS, MAX_RETRIES, DEFAULT_USER_ID, REPOS_BASE_PATH Legacy DB-poll mode only. Not used by queue workers.

Frontend

Variable Notes
USE_NEW_UI_TOOLS 1 = Gulp bundles under public/assets/dist/; 0 = legacy public/assets/js/ + style/.
MODE development or production — controls asset fingerprinting.
USE_AUTH_SYSTEM Enable auth.
SHOW_ERRORS, DEBUG_MODE Dev diagnostics. Must be off in production.
SOCKET_URL, SOCKET_PORT PHP WebSocket service (socket.php).
JWT_KEY JWT signing material and the GitHub-token encryption key fallback — see the contract table.
SENDGRID_API_KEY Outbound email (optional).
UPLOADER, UPLOADER_BASE_PATH Upload backend mode (e.g. local).
CLI_EXCHANGE, CLI_QUEUE, CLI_ROUTING_KEY CLI traffic queue wiring (cli_exchange / cli_queue / cli_routing_key).
GITHUB_APP_CLIENT_ID, GITHUB_APP_CLIENT_SECRET GitHub App OAuth; also optionally set on the upload processor for in-memory token refresh during clone.

On-chain (cert worker only)

Group Variables
Sepolia ETH_CERTIFY_ENABLED, SEPOLIA_RPC_URL, SEPOLIA_PRIVATE_KEY (or PRIVATE_KEY), CERTIFY_ADDRESS, ETH_CERTIFY_CHAIN_ID (default 11155111), ETH_CERTIFY_DEDUP_FROM_BLOCK, ETH_CERTIFY_DEDUP_REQUIRED (default fail-open)
Mainnet MAINNET_CERTIFY_ENABLED, MAINNET_RPC_URL, MAINNET_CERTIFY_ADDRESS, MAINNET_PRIVATE_KEY (or MAINNET_CERTIFY_PRIVATE_KEY), ETH_MAINNET_CHAIN_ID (default 1), MAINNET_CERTIFY_DEDUP_FROM_BLOCK, MAINNET_CERTIFY_DEDUP_REQUIRED (default fail-closed)

The asymmetry is deliberate: mainnet spends real gas, so if the dedup scan cannot confirm the bundle is new, the job retries rather than risk a duplicate anchor. Set *_DEDUP_FROM_BLOCK to the Certify deploy block — public RPCs cap eth_getLogs ranges and an earliest scan will fail. See Certify contract.

Deployment (atomizer AWS)

Non-secret per-environment config in deploy/config/<env>.env:

Variable Example Notes
ENV_NAME dev Used in resource names.
AWS_ACCOUNT_ID, AWS_REGION
CLUSTER_NAME verilib-atomizer-dev ECS cluster.
DESIRED_COUNT 1 Initial task count; the autoscaler manages it after.
TASK_ROLE_NAME, EXECUTION_ROLE_NAME Created manually; ARNs derived from the account id.
RABBITMQ_SECRET_ARN full ARN incl. 6-char suffix Secrets Manager entry holding RABBITMQ_USER / RABBITMQ_PASSWORD.
IMAGE_TAG latest or a git sha
WORKER_REPO, PROBE_*_REPO verilib/worker, … ECR repository names.
ATOMIZE_MIN_TASKS / ATOMIZE_MAX_TASKS 1 / 10
UPLOAD_MIN_TASKS / UPLOAD_MAX_TASKS 1 / 5
ATOMIZE_TARGET_MSG_PER_CONSUMER 0.5 See the note below.
UPLOAD_TARGET_MSG_PER_CONSUMER 1 Uploads are short; tolerate ~1 queued message per worker.
LAMBDA_FUNCTION_NAME, LAMBDA_ROLE_NAME, LAMBDA_SCHEDULE_RATE, METRIC_NAMESPACE verilib-queue-depth-publisher, …, 1 minute, Verilib/RabbitMQ Queue-depth publisher.

Target-tracking targets must be below 1

Target tracking keeps the metric at the target, not below it. ATOMIZE_TARGET_MSG_PER_CONSUMER=1 means "one backlog message per worker is acceptable — don't scale". Use a value < 1 (0.5 is the tracked default) for "scale out as soon as a second message waits". The value must be > 0; target tracking forbids 0.

Per-service CPU/memory are hardcoded in deploy/templates/*.taskdef.json rather than exposed here, because they encode the Docker-out-of-Docker packing rule: the atomize processor must reserve enough RAM to cover the probe container it spawns via /var/run/docker.sock.