Environment variables
Every .env key Fabrik reads — what it controls, whether it's required, and what to set it to in production.
All configuration lives in one .env file at the repository root. docker-compose.yml loads it for every service; Django reads it through os.environ at startup. This page walks through every key grouped by concern.
Keys marked required use the ${VAR:?} syntax in compose — missing them fails docker compose up immediately with a clear error, which is intentional.
Security
Never reuse the placeholder values from .env.example. Generate fresh secrets per deployment. Committing .env to git is a data breach.
| Key | Required | Purpose |
|---|---|---|
DJANGO_SECRET_KEY | ✅ | Django's signing key — JWT, CSRF, session cookies. Rotating invalidates all sessions. |
ENCRYPTION_KEY | ✅ | Fernet key for APIC passwords, AWX tokens, and other credential fields. Rotating requires re-encrypting every stored credential. |
Generate both:
python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"Django
| Key | Default | Purpose |
|---|---|---|
DEBUG | false | Must be false in production. true disables many security checks and exposes stack traces. |
ALLOWED_HOSTS | — (required) | Comma-separated hostnames. Requests to other hosts get Django 400. Use the public hostname, not the container name. |
CORS_ALLOWED_ORIGINS | — | Comma-separated URLs that can call the API from a browser. Usually matches the frontend origin. No wildcards in production. |
SITE_URL | — | Canonical URL Fabrik uses when it generates absolute links (email, password reset). |
SECURE_SSL_REDIRECT | true | Redirects HTTP to HTTPS at the Django layer. Leave on if you terminate TLS at nginx. |
SESSION_COOKIE_SECURE | true | Session cookies served over HTTPS only. |
CSRF_COOKIE_SECURE | true | CSRF cookie served over HTTPS only. |
SECURE_HSTS_SECONDS | 31536000 | HSTS max-age (1 year). Only enable once you're confident TLS is correct — browsers remember. |
Timezone
| Key | Default | Purpose |
|---|---|---|
TZ | UTC | Container timezone. Affects log timestamps, cron-style scheduled tasks, and the default if a user hasn't picked a timezone. |
DEFAULT_USER_TIMEZONE | UTC | Initial display timezone for newly created users. They can change it in Settings → Appearance. |
Scheduled tasks run in their configured timezone, not the server's — see Scheduled queries for the distinction.
PostgreSQL
| Key | Default | Purpose |
|---|---|---|
POSTGRES_DB | fabrik | Database name. |
POSTGRES_USER | fabrik | Database user. |
POSTGRES_PASSWORD | — (required) | Database password. |
POSTGRES_HOST | postgres | Container hostname. Keep as postgres unless you use an external database. |
POSTGRES_PORT | 5432 | Database port. |
DATABASE_URL | — (required) | Full connection string: postgresql://user:pass@host:port/dbname. |
DATABASE_URL is the source of truth, not POSTGRES_*. Django reads DATABASE_URL. The Postgres container, on the other hand, is initialised from POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB. If the password embedded in DATABASE_URL doesn't match POSTGRES_PASSWORD, the database boots fine but Django fails with password authentication failed for user "fabrik" on first migration. Always change them together.
Neo4j
| Key | Default | Purpose |
|---|---|---|
NEO4J_URI | bolt://neo4j:7687 | Bolt protocol URL. |
NEO4J_USER | neo4j | Neo4j username (can't be changed). |
NEO4J_PASSWORD | — (required) | Neo4j password. Set once; rotating requires restarting the container. |
NEO4J_HEAP_MAX_SIZE | 1G | JVM heap ceiling. Raise for large MIM imports. |
NEO4J_PAGECACHE_SIZE | 256M | Page cache. Bigger = faster queries, more RAM used. |
MIM registry
The MIM (Cisco ACI Managed Information Model) is not seeded from environment variables. After the stack is running, an admin loads the desired version through Settings → MIM Management → Cisco DevNet in the UI; ~17,500 classes stream from pubhub.devnetcloud.com directly into the local Neo4j on demand.
Redis and Celery
| Key | Default | Purpose |
|---|---|---|
REDIS_URL | redis://redis:6379 | Base Redis URL (used for Channels layer). |
CELERY_BROKER_URL | — (required) | Celery task queue. Usually redis://redis:6379/0. |
CELERY_RESULT_BACKEND | — (required) | Celery result storage. Usually redis://redis:6379/1. |
CELERY_WORKER_CONCURRENCY | 2 | Parallel task slots per worker container. Raise if query execution backs up. |
RabbitMQ
Used only for AWX event streaming. Separate from Celery to isolate high-frequency event traffic.
| Key | Default | Purpose |
|---|---|---|
RABBITMQ_USER | fabrik | Broker user. |
RABBITMQ_PASSWORD | — (required) | Broker password. |
RABBITMQ_VHOST | fabrik | Virtual host. |
RABBITMQ_URL | — (required) | Full AMQP URL: amqp://user:pass@host:port/vhost. |
Same gotcha as PostgreSQL: the AWX event consumer reads RABBITMQ_URL, while the broker container is initialised from RABBITMQ_USER / RABBITMQ_PASSWORD / RABBITMQ_VHOST. The password embedded in RABBITMQ_URL must match RABBITMQ_PASSWORD. Mismatch = the consumer logs ACCESS_REFUSED and AWX webhook events silently pile up unprocessed.
Email (SMTP)
| Key | Default | Purpose |
|---|---|---|
EMAIL_ENABLED | true | Master switch. false silently drops every email attempt. |
EMAIL_BACKEND | django.core.mail.backends.smtp.EmailBackend | Keep as SMTP; change only for testing. |
EMAIL_HOST | — | SMTP hostname. |
EMAIL_PORT | 587 | SMTP port. |
EMAIL_USE_TLS | true | STARTTLS. Set to false and use port 465 for SMTPS. |
EMAIL_HOST_USER / EMAIL_HOST_PASSWORD | — | Auth. |
DEFAULT_FROM_EMAIL | — | From: header on outbound mail. |
NOTIFICATION_EMAIL_ENABLED | true | Secondary switch — turns off only notification emails, leaves transactional (password reset, verification) on. |
LDAP
| Key | Default | Purpose |
|---|---|---|
LDAP_ENABLED | false | Master switch. Everything below is only read when this is true. |
LDAP_SERVER_URI | — | ldap://host or ldaps://host. |
LDAP_BIND_DN / LDAP_BIND_PASSWORD | — | Service account for directory lookups. Read-only access is enough. |
LDAP_USER_DN | — | Search base for user lookups. |
LDAP_GROUP_DN | — | Search base for group lookups. |
See LDAP integration for the full attribute map and mirroring behavior.
AWX integration
| Key | Default | Purpose |
|---|---|---|
AWX_WEBHOOK_SECRET | — | Shared secret AWX signs webhook callbacks with. Must match what you configure in AWX. |
AWX_DEFAULT_EXECUTION_MODE | bulk | Default execution mode for new automation templates. |
AWX_JOB_SYNC_INTERVAL | 10 | Seconds between job status polls (fallback if webhook is slow). |
AWX_USER_MAPPING | false | Map Fabrik users to AWX users via matching username. |
Platform
| Key | Default | Purpose |
|---|---|---|
FABRIK_VERSION | 1.0.0 | Shown in UI footer and /api/health/. Don't change unless you're forking. |
FABRIK_BASE_URL | — | External URL Fabrik is reachable at. Used in email links. Usually matches SITE_URL. |
Rate limiting
| Key | Default | Purpose |
|---|---|---|
RATELIMIT_ENABLE | true | Master switch for Django-level rate limits. nginx has its own limits on top. |
Individual endpoint throttles live in REST_FRAMEWORK['DEFAULT_THROTTLE_RATES'] in backend/fabrik/settings.py. Changing them requires editing the file and restarting the backend — they aren't .env keys.
Final sanity check
Before docker compose up:
- No value is still
change-meoryour-*-here. DATABASE_URLandRABBITMQ_URLcomponents match their individual fields.ALLOWED_HOSTScontains the hostname you'll actually reach Fabrik at.CORS_ALLOWED_ORIGINSuseshttps://— nothttp://.DEBUG=false.