11 KiB
Configuration
RenewCMS reads its configuration from environment variables. On startup it also loads a .env file from the working directory (or its parent) if one exists. Real environment variables take precedence over .env.
Copy .env.example to .env to get started. When a required variable is missing, the server refuses to start and names the variable.
Variables
Required
| Name | Description | Example |
|---|---|---|
PUBLIC_URL |
Public base URL of the site, including scheme and no trailing slash. Used for links in emails and in the startup log. | https://cms.example.com |
JWT_SECRET |
Secret used to sign the auth JWT. Use a long random value; changing it logs everyone out. | openssl rand -hex 32 |
SMTP_LOGIN |
SMTP username used to authenticate with the server. Some providers (e.g. Brevo) issue a login distinct from the sender address. | noreply@example.com |
SMTP_SENDER |
Sender address used in the From header. Must be an address/domain the SMTP provider allows that login to send as. |
noreply@example.com |
SMTP_PASSWORD |
SMTP password. | |
SMTP_HOST |
SMTP server host. | smtp.example.com |
SMTP_PORT |
SMTP server port. | 465 |
Optional
| Name | Default | Description |
|---|---|---|
ENVIRONMENT |
production |
production or development. In production the auth cookie is Secure, so it is only sent over HTTPS. development turns on HTTP request logging. |
PORT |
8080 |
Port the HTTP server listens on (all interfaces). |
DB_DRIVER |
sqlite |
sqlite or mysql (MySQL and MariaDB). Any other value stops the server with unsupported database driver "<value>". See Database. |
DB_FILE |
./data/renewcms.db |
Path to the SQLite database file. Missing parent directories are created. The schema is migrated automatically at startup. Ignored when DB_DRIVER=mysql. |
DATA_DIR |
directory of DB_FILE |
Directory for update state, staged updates and database backups (backups/). Set it with DB_DRIVER=mysql, where there is no database file to sit beside (the default still follows DB_FILE). With SQLite, leave it unset to keep everything beside the database. |
UPLOAD_DIR |
./data/uploads |
Directory where uploaded images are stored. |
CORS_ALLOWED_ORIGINS |
(empty) | Semicolon-separated list of origins allowed to call the API. When empty, no CORS headers are sent. That is the right setting whenever the SPA is served by RenewCMS itself (production) or through the Vite proxy (development). |
AUTH_RATE_LIMIT_MAX_ATTEMPTS |
5 |
Maximum number of requests a single client IP may send to POST /v1/auth/login, POST /v1/auth/password-reset or the invite-accept pair (GET /v1/auth/invite and POST /v1/auth/invite/accept, sharing one counter) within the window, tracked independently per route. |
AUTH_RATE_LIMIT_WINDOW_SECONDS |
60 |
Length, in seconds, of the fixed rate-limit window for the routes above. |
TRUSTED_PROXIES |
(empty) | Semicolon-separated list of exact IP addresses (no CIDR) of reverse proxies allowed to supply the client IP via X-Real-IP for the rate limiting of the routes above. When a request's connection address is not in this list, any X-Real-IP or X-Forwarded-For header it sends is ignored and the connection address is used instead. See deployment.md. |
RENEWCMS_CONTAINER |
false |
Set to true (case-insensitive; any other value means false) when the app runs in a container. Settings then tells the owner to update by pulling a new image instead of offering browser updates, and can_apply is always false. The Dockerfile and docker-compose.yml set it to true. Update checks still work. |
UPDATE_FEED_URL |
https://git.floriansylvain.fr |
Base URL of the Gitea server that publishes RenewCMS releases. The Settings page asks {UPDATE_FEED_URL}/api/v1/repos/fsylvain/RenewCMS/releases when the owner checks for updates. The last result is kept in update-status.json, in the data directory. |
Automatic media purge has no environment variable. Its schedule (on or off, interval, enabled_at, last_purge_at) lives in the purge settings row of the database, is changed from the Settings page or PATCH /v1/media/purge-settings, and is read again at every hourly tick.
Database
SQLite is the default and needs nothing but DB_FILE. To run against MySQL 8 or MariaDB 11 instead, set DB_DRIVER=mysql and the connection variables below. They are only read (and the four required ones only checked) when DB_DRIVER=mysql. A required variable that is not set stops the server with Environment variable DB_HOST is not set (or DB_USER, DB_PASSWORD, DB_NAME).
| Name | Default | Description |
|---|---|---|
DB_HOST |
required | Server host name or IP address. |
DB_PORT |
3306 |
Server port. |
DB_USER |
required | Database user. It needs full rights on DB_NAME, because the schema is created and migrated at startup. |
DB_PASSWORD |
required | Database password. It is never logged and never appears in an error message. |
DB_NAME |
required | Name of an existing database. The server does not create it. |
DB_TLS |
false |
false, true, skip-verify or preferred. Any other value stops the server with an error naming DB_TLS and these values. true verifies the server certificate; skip-verify encrypts without verifying it. |
DB_MAX_OPEN_CONNS |
10 |
Maximum open connections in the pool. 0 means unlimited. |
DB_MAX_IDLE_CONNS |
5 |
Maximum idle connections kept in the pool. |
DB_CONN_MAX_LIFETIME_SECONDS |
300 |
Seconds after which a connection is closed and replaced. 0 keeps connections forever. Keep it below the server's wait_timeout. |
A non numeric (or negative) pool value stops the server with an error naming the variable. There is no DSN variable: the connection string is built from the variables above, with parseTime=true, loc=UTC, charset=utf8mb4, collation=utf8mb4_bin and clientFoundRows=true. Connecting uses a 5 second timeout, so a wrong host, port, password or database name stops startup quickly with Unable to open the database, naming the host and port but never the password.
Why those settings: tables are created with the utf8mb4_bin collation so that usernames and emails compare case-sensitively exactly like in SQLite (Bob and bob are different users). The collation of DB_NAME itself does not matter, but tables that already exist keep the collation they have. clientFoundRows makes an update that does not change a row still count as matched, which the invite and purge-settings code relies on. Each session also drops the server's NO_ZERO_DATE SQL mode, because the Go driver sends a zero time as 0000-00-00. The other strict modes stay on.
With MySQL the data directory (DATA_DIR) holds update state and backups only. The pre-update backup is a renewcms-<time>.sql logical dump (schema and rows, written by RenewCMS itself, no mysqldump needed) that keeps the newest 3. If the dump fails, the update download fails with it. The dump holds your data, such as password hashes, but not the connection password.
Relative paths are resolved against the process's working directory. In production, use absolute paths (see deployment.md).
For example, WorkingDirectory=/var/lib/renewcms with DB_FILE=./data/renewcms.db puts the database in /var/lib/renewcms/data. Browser-update --data-dir must name that directory, because the app reads helper status and update records beside the database. It is not necessarily WorkingDirectory or ReadWritePaths. Do not change an existing DB_FILE merely to match the helper: that could open a different, empty database. See browser-update setup.
Adding a variable
- Read it in the code where it is needed (infrastructure or
api/, neverinternal/domain). - Register it in
cmd/server/main.go: add it torequiredEnvVarsif it is required (or toDatabaseRequiredEnvVarsininternal/infrastructure/configif only one database driver needs it), or toenvDefaultsif it has a default. - Add it to
.env.exampleand to the tables above.
Never hardcode a secret, and never commit .env.