Skip to content
OpENEMdocs

DocumentationFor hosts

Environment variables

Every environment variable OpENEM reads, with its default value and what it does, grouped by topic.

Where each variable is set

Compose does not pass the whole .env to the backend, only the variables listed under environment in docker-compose.yml, and some of them with a fixed value. In the tables, "fixed in Compose" means that changing the variable in .env has no effect, and "outside Compose" that it does not reach the container. To set one of those in Docker, create a docker-compose.override.yml at the root. Compose reads that file on its own, git ignores it, and its variables take precedence over the ones in docker-compose.yml:

services:
  backend:
    environment:
      - THROTTLE_LIMIT=200
      - PDF_CONCURRENCY=2

For numeric variables (rate limiting, PDF and AI quotas), a missing or invalid value falls back to the default.

Database

VariableDefaultWhat it does
POSTGRES_PASSWORDnoneRequired by Compose. PostgreSQL password, letters and digits only. Only applies when the pg-data volume is created.
POSTGRES_USERopenemPostgreSQL user in Compose.
POSTGRES_DBopenemDatabase name in Compose.
POSTGRES_PORT5432Database port published on 127.0.0.1, for the development backend, psql or pg_dump on the machine itself. The Compose backend uses the internal network.
DATABASE_URLnonePrisma connection URL. In Compose it is built from the three variables above; in development it comes from openem_backend/.env.

Cache (Redis)

VariableDefaultWhat it does
REDIS_PASSWORDnoneRequired by Compose. Redis (Valkey) password, letters and digits only.
REDIS_PORT6379Redis port published on 127.0.0.1, for the development backend. The Compose backend uses the internal network.
REDIS_MAXMEMORY128mbMaximum cache memory. When full, it drops the least used keys.
REDIS_URLnoneRedis URL, such as redis://:password@redis:6379. In Compose it is built from REDIS_PASSWORD. Without it, the cache lives in the API process memory, which is fine for a single instance.

The cache holds what is the same for every student: subjects, exams, search facets, the detail of each question and the star counts on the Tools screen. The seed, import and classification scripts invalidate it when they finish. If Redis goes down, the API reads from the database and /health shows redis: down without changing status.

Authentication

VariableDefaultWhat it does
JWT_SECRETnoneRequired by Compose. Signs the login tokens and encrypts the cookie of the Google sign-in round trip. With NODE_ENV set to production or test, the API does not start if it is missing or shorter than 16 characters; otherwise the API uses a fixed development secret. Changing it signs every student out.
JWT_EXPIRES_IN7dLogin token lifetime, such as 7d or 12h.

The token carries the account's session version, checked on every request. Changing the password, resetting it by e-mail or tapping Sair de todos os dispositivos (sign out of all devices) on the Profile screen invalidates every token already issued for that account right away, without waiting for JWT_EXPIRES_IN.

E-mail and Google sign-in

VariableDefaultWhat it does
SMTP_URLnoneMail server for password recovery, such as smtps://user:password@smtp.example.com:465 (implicit TLS) or smtp://user:password@smtp.example.com:587 (STARTTLS). Without it, POST /auth/forgot-password answers 503 and the sign-in screen hides Esqueci minha senha (forgot my password).
MAIL_FROMnoneSender of the e-mail, such as OpENEM <no-reply@openem.example>. Only applies together with SMTP_URL.
GOOGLE_CLIENT_IDnoneGoogle OAuth client ID. With GOOGLE_CLIENT_SECRET, it turns on the Entrar com Google button.
GOOGLE_CLIENT_SECRETnoneGoogle OAuth client secret.

Both features are optional and need SITE_URL: the e-mail link and the Google return address are built from it. A variable set without its pair turns the feature off and leaves a warning in the API log at startup. The step by step guide, including the Google consent screen, is in E-mail and Google sign-in.

Server and proxy

VariableDefaultWhat it does
PORT3000API port. Fixed in Compose.
NODE_ENVnone (production in the Docker image)production turns off the Swagger at /api and requires a valid JWT_SECRET. Fixed in Compose.
TRUST_PROXYnone (trusts no proxy)Number of proxies in front of the API (1) or a list of addresses and subnets (loopback, 10.0.0.0/8). true is rejected and keeps the API from starting. Fixed to 1 in Compose (2 with docker-compose.prod.yml); see Production.
CORS_ORIGINSnoneOrigins allowed by CORS, comma separated. Without a value, the API leaves CORS off in production (the app reaches it from the same origin through nginx) and accepts any origin outside production. * is rejected and stops the API from starting. Outside Compose.
SITE_URLnonePublic domain of the installation, such as https://openem.example. It goes into the frontend image build, where it generates sitemap.xml and makes the Open Graph URLs absolute, and the backend builds the password reset e-mail link and the Google sign-in return address from it. Without it, the site ships without a sitemap and with relative URLs, and those two features stay off (outside production, the backend uses http://localhost:5173). Changing it requires docker compose up --build.
SOURCE_URLthe official repositorySource code address the app shows on Login and Profile. The AGPL-3.0 requires that people using the app over the network can get the code running on it: if you modified OpENEM, point it at your fork. It goes into the frontend image build; changing it requires docker compose up --build.

Rate limiting

VariableDefaultWhat it does
THROTTLE_TTL_MS60000Window of the default limit, in milliseconds. Outside Compose.
THROTTLE_LIMIT100Requests allowed per window, counted per route and per student (per IP without login). Outside Compose.
THROTTLE_BURST_TTL_MS1000Window of the short burst limit, in milliseconds. Outside Compose.
THROTTLE_BURST_LIMIT20Requests allowed in a burst, per route and per student. Outside Compose.
THROTTLE_KEY_PREFIXnoneKeeps apart, in Redis, the counters of two installations that share the same server. Instances of the same installation must use the same value. Outside Compose.

These are the default limits. The expensive routes (sign-up, login, PDF, AI, forum and theme writes) have their own limits in the code, which these variables do not change; the table is in Production. The counters live in the Redis at REDIS_URL.

AI

VariableDefaultWhat it does
AI_PROVIDERautomatic detectionanthropic, openai, openai-compatible or gemini. Applies to question explanations and topic classification.
AI_EXPLANATION_MODELthe provider's modelModel for question explanations. Outside Compose.
AI_CLASSIFY_MODELthe provider's modelModel for the topic classification script. Outside Compose.
ANTHROPIC_API_KEYnoneAnthropic (Claude) key.
ANTHROPIC_MODELclaude-opus-4-8Default Anthropic model.
OPENAI_API_KEYnoneOpenAI key. Optional in compatible mode.
OPENAI_MODELgpt-4o on OpenAI, llama3.1 in compatible modeDefault model for OpenAI and for the compatible endpoint.
OPENAI_BASE_URLnoneBase URL of an endpoint compatible with the OpenAI API (Ollama, Groq, OpenRouter, LM Studio, vLLM). Enables the openai-compatible provider.
GEMINI_API_KEYnoneGoogle Gemini key.
GOOGLE_API_KEYnoneAlternative to GEMINI_API_KEY, used when that one is not set. Outside Compose; in Docker, use GEMINI_API_KEY.
GEMINI_MODELgemini-2.5-flashDefault Gemini model.

Details in Artificial intelligence.

AI quotas

VariableDefaultWhat it does
AI_EXPLANATION_DAILY_LIMIT30Question explanations generated per student in a 24-hour window. Opening an explanation that is already stored does not count. 0 turns this limit off.
AI_DAILY_LIMIT0 (no cap)Global cap: the sum of paid calls from all students in 24 hours.

PDF

VariableDefaultWhat it does
PDF_CONCURRENCY1How many PDFs are drawn at the same time, each in a worker thread. Minimum 1. Outside Compose.
PDF_MAX_QUEUE8How many requests can wait in the queue. Beyond that, the API answers 503 right away. Minimum 0. Outside Compose.
PDF_TIMEOUT_MS60000Time limit for each drawing, in milliseconds; when it runs out, the API answers 503. Minimum 1000. Outside Compose.

The queue covers both the simulation booklet and the formula booklet. Raise PDF_CONCURRENCY only if the server has spare cores.

Import and data

VariableDefaultWhat it does
AUTO_IMPORTon (1 in Compose)Turns on the bootstrap of the container's first start (seed, import, keyword classification and flashcards). 0, false, off or no turn it off.
ENEM_DATASET_DIRprisma/.enem-datasetWhere the import keeps the downloaded dataset. Fixed to /data/enem-dataset in Compose.
ENEM_MEDIA_DIRpublic/enemWhere the question images live, served by the API at /media/enem/. Fixed to /data/enem-media in Compose.
SEED_DEMOnoneWith 1, the seed creates the demo content of the e2e suite (6 questions and 2 simulations). Outside Compose.

Default paths are relative to openem_backend/.

Suggest a fix for this page