bottleneckcrm/docs/self-hosting/environment-variables.md
Ashwin e5b6beb4ca Refactor permissions and remove unused settings
- Replaced custom admin check with `is_org_admin` in `custom_field_views.py`, `organization_views.py`, `user_views.py`, and `macros/views.py` for consistency and clarity.
- Removed the unused `_is_admin` function from `custom_field_views.py` and `macros/views.py`.
- Updated organization-related views to streamline admin checks and improve error handling.
- Removed `SWAGGER_ROOT_URL` and `GOOGLE_REDIRECT_URI` settings from `settings.py` as they were not utilized.
- Cleaned up Swagger parameters across various modules to remove the now-irrelevant `org` header.
- Adjusted documentation to reflect the removal of `GOOGLE_REDIRECT_URI` and clarify Google OAuth configuration.
- Added database indexes for `Order` and `OrderLineItem` models to improve query performance.
2026-08-05 09:11:11 +05:30

5.3 KiB

Environment variables

backend/crm/settings.py is read once, at process import time, by every Django entry point: manage.py runserver, manage.py migrate, a Gunicorn/Uvicorn worker, a Celery worker, a management command. This page explains where those values come from and which ones you actually have to set yourself; the exhaustive per-variable table lives in the Reference section.

How configuration is loaded

backend/crm/settings.py calls load_dotenv() (from python-dotenv) near the top of the module, before any setting is read, so a .env file next to manage.py, created by copying backend/.env.example, per Manual setup, is loaded into the process environment automatically. docker-compose.yml does the equivalent for containers by listing .env.docker and then the optional, gitignored .env.docker.local under each service's env_file:, in that order, so values in .env.docker.local win.

Almost every setting in crm/settings.py follows the same pattern: os.environ.get("NAME", <default>), so an unset variable falls back to a working (usually dev-oriented) default rather than failing. SECRET_KEY, DEBUG, ALLOWED_HOSTS, DBNAME/DBUSER/DBPASSWORD/DBHOST/DBPORT, EMAIL_BACKEND, CELERY_BROKER_URL/CELERY_RESULT_BACKEND, GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET, DOMAIN_NAME and FRONTEND_URL are all read this way. A smaller set of variables has no default at all: backend/crm/server_settings.py (imported only when ENV_TYPE=prod, see Production deployment) reads AWS_BUCKET_NAME, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SES_REGION_NAME, AWS_SES_REGION_ENDPOINT and SENTRY_DSN with plain os.environ[...]. A missing one is a KeyError at import time, which means the process refuses to start at all rather than starting with something half-configured.

Because all of this runs at import time, not per-request, a changed environment variable only takes effect the next time the process restarts: editing .env while runserver is already running does nothing until you restart it (or, for the Docker Compose setup, until the container restarts, which happens automatically on docker compose up --build).

The minimum for a working install

The values in .env.example and .env.docker are enough to bring the stack up and use it locally; you do not need to touch anything else to follow Docker quick start or Manual setup. Before anyone other than you can reach the instance, the variables that actually need a real value are: SECRET_KEY (a unique secret, not the shipped django-insecure-... placeholder), DEBUG=False, ALLOWED_HOSTS (your real hostname(s)), CSRF_TRUSTED_ORIGINS (each entry needs a scheme, e.g. https://crm.example.com), DBUSER pointed at a non-superuser PostgreSQL role, and CELERY_BROKER_URL/CELERY_RESULT_BACKEND pointed at a Redis instance you control. Production deployment covers the full list and exactly what each one guards against; PostgreSQL and RLS covers why the database role matters as much as any of the Django settings.

Database

DATABASES["default"] in crm/settings.py reads DBNAME (default crm_db), DBUSER (default postgres), DBPASSWORD (default postgres), DBHOST (default localhost) and DBPORT (default 5432). The default DBUSER is the PostgreSQL superuser account on most installations, and a superuser silently bypasses every Row-Level Security policy this project relies on for tenant isolation. See PostgreSQL and RLS for how to create and verify a non-superuser role before pointing these variables at anything but a local, throwaway database.

Email

EMAIL_BACKEND defaults to django.core.mail.backends.console.EmailBackend, so outbound mail (welcome emails, magic-link sign-in emails, comment-mention notifications) prints to stdout instead of being sent anywhere. .env.example sets EMAIL_BACKEND to that same console backend explicitly; .env.docker doesn't set EMAIL_BACKEND at all, only a comment noting the console backend is what's active, so it's relying on the identical default rather than setting it. DEFAULT_FROM_EMAIL and ADMIN_EMAIL default to noreply@localhost/admin@localhost. Switching to a real mail backend, and the Amazon SES-specific variables that come with it, is covered in full in Email and Celery.

Integrations

GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET both default to empty strings in crm/settings.py, and .env.docker and .env.example ship both blank. Google sign-in stays off until they are set, on both the backend and the SvelteKit frontend. There is no GOOGLE_REDIRECT_URI setting: the redirect URI used in the token exchange is the one the frontend sends in the request body on each call, so a setting of that name was read into Django and never referenced again. See Google OAuth for what each one does and where to get the values. SENTRY_DSN is read (with no default) only inside server_settings.py, i.e. only when ENV_TYPE=prod; it's not consulted at all otherwise.

Full reference

Every variable this project reads, its default, and the file that reads it is tabulated in full in the Reference section.