-
Notifications
You must be signed in to change notification settings - Fork 6
Configuration
Every setting barakoCMS reads, generated from the 4.0.1 source on master. Each key is given in
config form (Jwt:Key style, what you put in appsettings.json) and environment form (replace each
: with a double underscore). An array element takes an index: ForwardedHeaders__KnownNetworks__0.
Start here: Home. For what changed in this major, see barakoCMS 4.0.
Three defaults account for most first-deployment surprises. All three are verifiable in the source and each is covered in its own section below.
-
JWT__Keymust be 32 characters or more, or the process exits at startup. A value starting withREPLACE_THISis also refused, because the shipped k8s secret carries a length-valid placeholder. -
Seed__DemoContentis on in Development and off everywhere else. Turning it on seeds a demo content type and an active workflow that sends email, so in production it adds an outbound mail path. -
The refresh cookie is marked
Secureoutside Development. A Production API served over plain HTTP cannot keep a browser session, because the browser will not send the cookie back.
Ordinary .NET configuration: appsettings.json, then appsettings.{Environment}.json, then
environment variables. The shipped defaults live in
barakoCMS/appsettings.json,
and appsettings.Development.json overrides only Swagger:Enabled.
Two things are not ordinary:
Modules see only their own section. A module is handed Modules:{Name} and nothing else, so the
connection string and the signing key are out of its reach
(ModuleConfiguration).
Some modules also declare a legacy root section they used to read; when the scoped section is empty
and the legacy one is not, the legacy one is used and a warning names both. When both exist the keys
are merged and the scoped value wins. The module tables below give the scoped key and the legacy key
where one exists.
One key can be overridden from the database. Kubernetes__Enabled is read through
IConfigurationService, which checks a SystemSetting document first, then configuration, then the
default
(ConfigurationService.cs).
Nothing else in the core uses that path.
SKIP_SEEDER is read straight from the environment, not from configuration. Any non-empty value
skips the startup seeder entirely
(Program.cs).
| Key | Environment | What it does | Default | Refuses to start without it |
|---|---|---|---|---|
ConnectionStrings:DefaultConnection |
ConnectionStrings__DefaultConnection |
Postgres connection string | empty | Yes outside Development. In Development a dummy string is used so design-time tooling works |
DATABASE_URL |
DATABASE_URL |
postgres:// URL, parsed into a connection string; wins over DefaultConnection
|
unset | No |
Tenancy:DatabaseEnforcement |
Tenancy__DatabaseEnforcement |
Row level security policies per tenant | false |
Yes when on and the app connects as a Postgres superuser, since a policy cannot bind a superuser |
EventSourcing:DocumentTypesAppend |
EventSourcing__DocumentTypesAppend |
Whether document content types also append events | true |
No |
DATABASE_URL defaults sslmode to Require and refuses an unrecognised sslmode. Npgsql's
IncludeErrorDetail is on only in Development on that path, so parameter values do not reach logs in
production
(ResolveConnectionString).
Schema handling is not configurable: Development uses Marten's CreateOrUpdate, everything else uses
CreateOnly, which creates missing objects and never alters an existing one.
| Key | Environment | What it does | Default | Refuses to start without it |
|---|---|---|---|---|
JWT:Key secret
|
JWT__Key |
Signing key for access tokens, preview tokens and the MFA challenge token | none |
Yes. Missing, under 32 characters, or starting with REPLACE_THIS all throw |
JWT:Issuer |
JWT__Issuer |
Issuer claim, validated on every token | https://localhost:7049/ |
No |
JWT:Audience |
JWT__Audience |
Audience claim, validated on every token | https://localhost:7049/ |
No |
Auth:RequireEmailVerification |
Auth__RequireEmailVerification |
Self-registration must prove its address | true |
Yes when set to false without the acknowledgement below |
Auth:AcknowledgeUnverifiedRegistration |
Auth__AcknowledgeUnverifiedRegistration |
Confirms you meant to turn verification off | false |
See above |
Auth:LegacyRoleFallback |
Auth__LegacyRoleFallback |
Fall back to role names when a capability check fails | false |
No |
Roles:RefuseUnknownCapabilities |
Roles__RefuseUnknownCapabilities |
Reject a role write naming a capability this instance does not know | false |
No |
DeviceTrust:Enforce |
DeviceTrust__Enforce |
Password sign-in requires a trusted device (DeviceTrust module) | false |
No |
Branding:AppName |
Branding__AppName |
Name used in OTP issuers and security emails | BarakoCMS |
No |
JwtKeyGuard.cs
is the whole rule, and it runs from AddBarakoCMS:
public const int MinLength = 32;
if (string.IsNullOrWhiteSpace(key) || key.Length < MinLength)
{
throw new System.InvalidOperationException(
$"JWT:Key must be configured and at least {MinLength} characters (256 bits) for security.");
}The second check refuses any key beginning REPLACE_THIS. The reason is in the same file: the shipped
k8s/02-secret.yaml placeholder is 45 characters, so an operator who applies the manifests unedited
would otherwise boot on a key published in this repository.
The key is read as JWT:Key first and then as the JWT__Key environment variable directly
(ServiceCollectionExtensions.cs),
so both forms work even where the environment provider is not wired up.
Sign-in sets barako_refresh, HttpOnly, SameSite=Lax, scoped to /api/auth/refresh. Whether it
carries Secure is decided by the environment, not by the scheme of the request:
internal static bool IsSecure(HttpContext http) =>
!http.RequestServices.GetRequiredService<IHostEnvironment>().IsDevelopment();The file explains why it does not follow Request.IsHttps: behind a TLS-terminating proxy that is not
forwarding headers, an HTTPS request arrives as HTTP and the cookie would ship without Secure on the
deployment that needs it most.
The practical consequence for you: a Production or Staging API reached over plain http:// sets a
Secure cookie the browser will not send back, so browser sessions cannot refresh. Either put TLS in
front of it, or run the API in Development. The refresh token is also in the response body, so
non-browser callers are unaffected.
All four are secrets. None of them belongs in the repository.
| Key | Environment | What it does | Default | Refuses to start without it |
|---|---|---|---|---|
Secrets:Key secret
|
Secrets__Key |
Key material for credentials an admin typed into the API (the email API key) | falls back to JWT:Key
|
No, because of the fallback |
Mfa:Key secret
|
Mfa__Key |
Key material for stored TOTP secrets | falls back to JWT:Key
|
No, because of the fallback |
Connectors:Key secret
|
Connectors__Key |
Key material for connector credentials. Unset means connector credentials cannot be encrypted and those endpoints refuse | unset | Yes when set to fewer than 32 characters, or to the same value as JWT:Key, Mfa:Key or Secrets:Key
|
Metrics:ScrapeKey secret
|
Metrics__ScrapeKey |
Shared key for /metrics, sent as Authorization: Bearer or X-Metrics-Key
|
unset, and unset means /metrics serves nobody |
No |
Rotating any of these makes everything already encrypted under it unreadable. That is why
Connectors:Key is refused when it duplicates another key: one rotation would otherwise retire two
unrelated controls
(ConnectorOptions.cs).
| Key | Environment | What it does | Default | Refuses to start without it |
|---|---|---|---|---|
CORS:AllowedOrigins |
CORS__AllowedOrigins |
Comma-separated browser origins allowed with credentials |
http://localhost:3000,http://localhost:3001,https://localhost:7049; an empty value falls back to those same three |
No |
AllowedHosts |
AllowedHosts |
Semicolon-separated Host headers the API answers to |
* (any) |
No |
App:BaseUrl |
App__BaseUrl |
This deployment's public origin, used for every absolute link the API hands out | empty | No when unset, but with AllowedHosts at * the feed and sitemap then answer 503 instead of guessing an origin. A malformed value throws per request instead; see below |
ForwardedHeaders:Enabled |
ForwardedHeaders__Enabled |
Read X-Forwarded-For and X-Forwarded-Proto
|
false |
Yes when on and neither known-proxy list is set |
ForwardedHeaders:KnownProxies |
ForwardedHeaders__KnownProxies__0 |
Trusted proxy IP addresses | empty | A non-IP value throws |
ForwardedHeaders:KnownNetworks |
ForwardedHeaders__KnownNetworks__0 |
Trusted proxy networks in CIDR form | empty | A non-CIDR value throws |
ForwardedHeaders:ForwardLimit |
ForwardedHeaders__ForwardLimit |
How many hops to trust | 1 |
No |
Hsts:MaxAgeDays |
Hsts__MaxAgeDays |
HSTS max-age, applied by UseHsts outside Development |
90 |
Yes outside Development when zero or negative, since that would switch HSTS off while looking like tightening. A Development host starts, because the check never runs |
Hsts:IncludeSubDomains |
Hsts__IncludeSubDomains |
Adds includeSubDomains
|
false |
No |
Multitenancy:RefuseUnknownHosts |
Multitenancy__RefuseUnknownHosts |
Refuse a host matching no tenant instead of serving the default tenant | false |
No |
Multitenancy:CacheDuration |
Multitenancy__CacheDuration |
How long the domain-to-tenant map is cached | 5 minutes | No |
CORS:AllowedOrigins also governs which response headers scripts may read: ETag and
X-Api-Contract-Version are exposed on both the configured and the fallback branch.
Leaving AllowedHosts at * is what makes App:BaseUrl necessary. With host filtering wide open
the API will not build links from the caller's Host header, so it returns nothing rather than let
the caller choose the origin
(CanonicalHost.cs).
Unset and malformed are two different failures. Unset is the 503 above, with a message naming the key
to set. A value that is set but is not an absolute http or https URL throws
InvalidOperationException instead, and the same check covers Feeds:SiteUrl. api.example.com with
no scheme is the one to watch for: it parses as a relative URI, so it throws rather than being read as
a host. The throw happens inside the request, not at startup, so the feed 500s while the host looks
healthy. The message names the key and the value.
| Key | Environment | What it does | Default | Refuses to start without it |
|---|---|---|---|---|
InitialAdmin:Username |
InitialAdmin__Username |
Username of the seeded SuperAdmin. Read only on an empty user table | admin |
No |
InitialAdmin:Password secret
|
InitialAdmin__Password |
Its password, same condition. Empty means one is generated and printed once to the container console | empty | No |
Seed:DemoContent |
Seed__DemoContent |
Seed the demo content type, records and workflow | on in Development, off everywhere else | No |
| (environment only) | SKIP_SEEDER |
Any non-empty value skips the seeder | unset | No |
SeedUsersAsync counts users before it reads anything:
var userCount = await session.Query<User>().CountAsync();
if (userCount > 0)
{
Console.WriteLine("[DataSeeder] Users already exist, skipping user seeding");
return;
}The InitialAdmin section is read after that return, so on a database with even one user both keys
are ignored. Changing InitialAdmin__Password on a running deployment does nothing, and changing
InitialAdmin__Username neither renames the account nor creates a second one. Reset a forgotten
admin password through the API or the database, not through this key.
The generated-password note has the same condition. It is printed on the boot that creates the
account and never again, it goes to Console.WriteLine rather than the logger so it does not reach a
log sink, and it is not recoverable afterwards.
Roles are seeded on every boot whatever Seed:DemoContent says, and SeedRolesAsync backfills
system capabilities onto roles that already exist. That is separate from the user gate above.
The default comes from DataSeeder.cs:
return configuration.GetValue("Seed:DemoContent", environment.IsDevelopment());When it is on, three things are seeded:
-
AttendanceRecord, a content type withFirstName,LastName,Email,BirthDay,JobDescription,GenderandSSN, whereSSNis marked Sensitive and masked to its last four characters. - Three sample records, deliberately unusable as personal data (
Sample Employee Oneand friends, mail atexample.com, SSN values that readSAMPLE-NOT-A-REAL-SSN-1). - A workflow named
Attendance Confirmation Email, stored active, triggering onAttendanceRecordCreatedwith conditionstatus = Published, whose single action isSendEmailto{{data.Email}}.
That last one is the reason to leave it off in production. Once a mail provider is configured, the
demo fixture is an outbound mail path: any published AttendanceRecord mails whatever address its
Email field holds. The quickstart compose file sets Seed__DemoContent to false explicitly even
though it already runs as Production.
Sample login accounts (hr_manager, john_viewer, with fixed passwords) are separate: they are
seeded only when ASPNETCORE_ENVIRONMENT is Development and the user table is empty, and
Seed:DemoContent does not control them.
| Key | Environment | What it does | Default | Refuses to start without it |
|---|---|---|---|---|
BarakoCMS:Modules:Enabled |
BarakoCMS__Modules__Enabled |
Which modules run, as an array or a comma-separated string | unset means every module found runs, with one warning. "" means core only |
Yes when a listed name matches no module |
BarakoCMS:Modules:Discover |
BarakoCMS__Modules__Discover |
Scan the dependency context for modules | true |
No |
BarakoCMS:Modules:SchemaPreflight |
BarakoCMS__Modules__SchemaPreflight |
Report, per module, the schema migration Marten would apply | unset means on when the store is CreateOnly, which is every environment except Development |
No |
Do not map BarakoCMS__Modules__Enabled from an optional shell variable. An unset variable arrives
as an empty string, and an empty string means core only. The quickstart compose file leaves it
commented out for exactly this reason.
Resend:* is read at the root by the core, not by the Resend module, so these keys keep their
root-level form.
| Key | Environment | What it does | Default | Refuses to start without it |
|---|---|---|---|---|
Resend:ApiKey secret
|
Resend__ApiKey (also plain RESEND_API_KEY) |
Resend API key. A key stored through the admin wins over this | unset | No |
Resend:From |
Resend__From |
Default sender address. A stored from address wins | unset | No |
Resend:WebhookSecret secret
|
Resend__WebhookSecret (also plain RESEND_WEBHOOK_SECRET) |
Verifies Resend delivery webhooks | unset, and unset means the webhook answers 401 to everyone | No |
Modules:Email.Smtp:Host |
Modules__Email.Smtp__Host |
SMTP relay hostname. Blank means the module registers no provider at all | unset | No |
Modules:Email.Smtp:Port |
Modules__Email.Smtp__Port |
Submission port | 587 |
No |
Modules:Email.Smtp:User |
Modules__Email.Smtp__User |
Relay username | unset | No |
Modules:Email.Smtp:Password secret
|
Modules__Email.Smtp__Password |
Relay password | unset | No |
Modules:Email.Smtp:From |
Modules__Email.Smtp__From |
Sender address. A from address stored in the admin wins | unset | No |
Modules:Email.Smtp:Security |
Modules__Email.Smtp__Security |
None, StartTls or SslOnConnect
|
unset, which picks implicit TLS on port 465 and STARTTLS elsewhere | No |
With no provider module configured, the core registers a mock email service that logs instead of sending.
| Key | Environment | What it does | Default | Refuses to start without it |
|---|---|---|---|---|
Modules:Files.S3:Bucket (legacy Files:S3:Bucket) |
Modules__Files.S3__Bucket |
Bucket name. Blank leaves the module dormant and Postgres storage serving | unset | No |
Modules:Files.S3:ServiceUrl |
Modules__Files.S3__ServiceUrl |
Endpoint for R2 or MinIO. Leave unset for AWS | unset | No |
Modules:Files.S3:Region |
Modules__Files.S3__Region |
AWS region, used only when ServiceUrl is unset |
us-east-1 |
No |
Modules:Files.S3:AccessKey secret
|
Modules__Files.S3__AccessKey |
Access key | empty | No |
Modules:Files.S3:SecretKey secret
|
Modules__Files.S3__SecretKey |
Secret key | empty | No |
Modules:Files.S3:ForcePathStyle |
Modules__Files.S3__ForcePathStyle |
Path-style addressing, needed by MinIO and R2 | true |
No |
Modules:Files.S3:PublicBaseUrl |
Modules__Files.S3__PublicBaseUrl |
Public base URL for public objects. Unset proxies them through the API | unset | No |
Modules:Files.S3:UsePublicReadAcl |
Modules__Files.S3__UsePublicReadAcl |
Set a public-read ACL on public objects. Turn off for R2, which ignores object ACLs | true |
No |
Modules:Files:Files:Scanner:Address |
Modules__Files__Files__Scanner__Address |
Registers the clamd scanner. Needed together with the root key below; see the section after this table | empty | No |
Files:Scanner:Address |
Files__Scanner__Address |
host:port the registered scanner connects to. Needed together with the scoped key above |
empty | No |
Files:Scanner:TimeoutSeconds |
Files__Scanner__TimeoutSeconds |
Whole-exchange timeout for a scan, which is also how long an upload waits. Read at the root, like the address | 30 |
No |
Files:Images:MaxWidth |
Files__Images__MaxWidth |
Widest ?w= a caller may ask for. Zero turns variants off |
2048 |
No |
Files:Images:MaxSourcePixels |
Files__Images__MaxSourcePixels |
Largest source image that gets resized at all; larger ones are served unresized | 50000000 |
No |
RequestLimits:MaxBodyBytes |
RequestLimits__MaxBodyBytes |
Kestrel request body limit and multipart limit | 10 MB | No |
Import:MaxExpandedBytes |
Import__MaxExpandedBytes |
Largest uncompressed spreadsheet size an upload may declare | 8 MB | No |
Set both, to the same value:
Modules__Files__Files__Scanner__Address=clamav:3310
Files__Scanner__Address=clamav:3310
They do different jobs, and the reason is the module scoping described at the top of this page.
FilesModule declares no legacy root section, so it is handed Modules:Files and nothing else. The
literal it reads is Files:Scanner:Address, which against that section resolves to
Modules:Files:Files:Scanner:Address. Blank there registers NoFileScanner, set there registers
ClamAvScanner
(FilesModule.cs).
ClamAvScanner then reads the same literal from the root configuration its constructor is given, so
for it the key is plain Files:Scanner:Address, and that read is where it gets a host
(ClamAvScanner.cs).
Either key on its own scans nothing:
- Root key only.
NoFileScanneris registered and itsConfiguredis false. - Scoped key only.
ClamAvScanneris registered, finds no address at the root, and itsConfiguredis false too.
The upload path scans only if (_scanner.Configured), so in both cases it skips scanning and stores
the file. Nothing logs a warning about it. With both keys set, an infected file gets 422 and a
scanner that cannot be reached gets 503, so a test upload tells you which state you are in.
| Key | Environment | What it does | Default | Refuses to start without it |
|---|---|---|---|---|
Content:Concurrency:Require |
Content__Concurrency__Require |
Require If-Match on a content update, answering 428 without it |
false |
No |
Lifecycle:EnforceTransitions |
Lifecycle__EnforceTransitions |
Refuse a transition whose From state does not match, with 409 |
true |
No |
Lifecycle:AllowSelfTransition:{name} |
Lifecycle__AllowSelfTransition__{name} |
Let the creator of an entry run the named transition on it | false |
No |
PublicDelivery:RequireAcknowledgement |
PublicDelivery__RequireAcknowledgement |
Turning public delivery on for a type needs acknowledgeExposure
|
false |
No |
Sensitivity:Mode |
Sensitivity__Mode |
Sensitivity enforcement mode; an unparseable value falls back to the default | SensitiveOnly |
No |
Delivery:MaxRadiusKm |
Delivery__MaxRadiusKm |
Widest near radius a public query may ask for; a non-positive value reads as unset |
1000 |
No |
Delivery:Events:Enabled |
Delivery__Events__Enabled |
The anonymous SSE content-change stream | false |
No |
Delivery:Events:MaxConnections |
Delivery__Events__MaxConnections |
Open streams across all tenants; the next gets 503 | 100 |
No |
Delivery:Events:MaxConnectionsPerClient |
Delivery__Events__MaxConnectionsPerClient |
Open streams per client address; zero turns the per-client cap off | 5 |
No |
Delivery:Events:KeepAliveSeconds |
Delivery__Events__KeepAliveSeconds |
Keep-alive interval on a stream | 15 |
No |
Feeds:SiteUrl |
Feeds__SiteUrl |
Front-end origin for feed and sitemap links; preferred over App:BaseUrl
|
unset | No |
Feeds:Paths:{type} |
Feeds__Paths__{type} |
Link template per content type, such as /blog/{slug}
|
/{type}/{slug} |
No |
Feeds:Titles:{type} |
Feeds__Titles__{type} |
RSS channel title for that content type's feed | the type name | No |
Blueprints:Path |
Blueprints__Path |
Directory of custom *.json content-type blueprints, read on every list |
unset | No |
Erasure:Mode |
Erasure__Mode |
Delete, None or CryptoShred
|
Delete |
Yes for an unrecognised value, for CryptoShred (not implemented), and for None without the acknowledgement below |
Erasure:AcknowledgeNoErasure |
Erasure__AcknowledgeNoErasure |
Confirms you meant Erasure:Mode=None
|
false |
See above |
| Key | Environment | What it does | Default | Refuses to start without it |
|---|---|---|---|---|
Workflows:RunnerEnabled |
Workflows__RunnerEnabled |
The background workflow runner | true |
No |
Workflows:Retention:Enabled |
Workflows__Retention__Enabled |
Sweep old workflow runs | true |
No |
Workflows:Retention:Succeeded |
Workflows__Retention__Succeeded |
Days to keep a succeeded run | 7 |
No |
Workflows:Retention:Failed |
Workflows__Retention__Failed |
Days to keep a failed run | 90 |
No |
Webhooks:DeliveryLogRetentionDays |
Webhooks__DeliveryLogRetentionDays |
Days to keep a webhook delivery row; zero or less keeps forever | 30 |
No |
Webhooks:ResponseBodyRetentionHours |
Webhooks__ResponseBodyRetentionHours |
Hours to keep a delivery's response body; zero or less keeps forever | 24 |
No |
Webhooks:AllowProxy |
Webhooks__AllowProxy |
Let outbound webhook calls use a system proxy, which bypasses the outbound address guard | false |
No |
Webhooks:AllowInsecureSignedUrls |
Webhooks__AllowInsecureSignedUrls |
Allow a signed webhook URL over plain HTTP | false |
No |
Jobs:MaxAttempts |
Jobs__MaxAttempts |
Attempts before a job is dead-lettered | 5 |
Throws when below 1, at the point the job queue resolves its options |
Jobs:BackoffBaseSeconds |
Jobs__BackoffBaseSeconds |
Wait after the first failure, doubling after that | 30 |
Throws when negative |
Jobs:BackoffMaxSeconds |
Jobs__BackoffMaxSeconds |
Longest wait between attempts | 3600 |
Throws when below Jobs:BackoffBaseSeconds
|
Jobs:StorageProbeSeconds |
Jobs__StorageProbeSeconds |
How often a worker re-reads storage for due retries and other instances' jobs | 60 |
Throws when below 1 |
Jobs:LeaseSeconds |
Jobs__LeaseSeconds |
How long a claimed job stays claimed, which is also the handler's time limit | 600 |
Throws when below 1 |
| Key | Environment | What it does | Default | Refuses to start without it |
|---|---|---|---|---|
Swagger:Enabled |
Swagger__Enabled |
The Swagger document and UI, and what GET /api/meta reports |
on when ASPNETCORE_ENVIRONMENT is Development, off otherwise |
No |
Serilog:WriteToFile |
Serilog__WriteToFile |
Also write a daily rolling file under logs/
|
false |
No |
Serilog:MinimumLevel:* |
Serilog__MinimumLevel__Default |
Standard Serilog configuration, read from appsettings.json
|
Information, with Microsoft, System, FastEndpoints, Marten and Npgsql at Warning
|
No |
HealthChecksUI:Enabled |
HealthChecksUI__Enabled |
The health dashboard at /health-ui
|
false |
No |
HealthChecks:MaxPrivateMemoryMegabytes |
HealthChecks__MaxPrivateMemoryMegabytes |
Private-memory ceiling for the memory check | 4096 |
No |
HealthChecks:MinimumFreeDiskMegabytes |
HealthChecks__MinimumFreeDiskMegabytes |
Free-space floor on /
|
512 |
No |
HealthChecks:MaxProjectionLagEvents |
HealthChecks__MaxProjectionLagEvents |
Projection lag tolerated before the workflow projection check reports Degraded | 1000 |
No |
Metrics:ScrapeKey secret
|
Metrics__ScrapeKey |
See Secrets above. Unset means /metrics refuses everyone |
unset | No |
Kubernetes:Enabled |
Kubernetes__Enabled |
Cluster monitoring behind /api/monitoring/k8s. A SystemSetting row of the same key overrides configuration |
false |
No |
/health, /health/live and /health/ready are always mapped and are not configurable. Only the
memory check is tagged live; the database, disk, memory and startup-seeding checks are ready.
| Key | Environment | What it does | Default | Refuses to start without it |
|---|---|---|---|---|
ExternalAuth:Enabled |
ExternalAuth__Enabled |
Master kill switch for social sign-in. Only the literal false disables; unset leaves providers on |
unset | No |
Google:ClientId / Google:ClientSecret secret
|
Google__ClientId / Google__ClientSecret
|
Google sign-in. A provider is on when its client id is set | unset | No |
GitHub:ClientId / GitHub:ClientSecret secret
|
GitHub__ClientId / GitHub__ClientSecret
|
GitHub sign-in | unset | No |
Facebook:AppId / Facebook:AppSecret secret
|
Facebook__AppId / Facebook__AppSecret
|
Facebook sign-in | unset | No |
LinkedIn:ClientId / LinkedIn:ClientSecret secret
|
LinkedIn__ClientId / LinkedIn__ClientSecret
|
LinkedIn sign-in | unset | No |
{Provider}:Enabled |
{Provider}__Enabled |
Set to false to keep one configured provider dark |
unset | No |
Facebook:TrustUnverifiedEmail |
Facebook__TrustUnverifiedEmail |
Treat a Facebook address as verified | false |
No |
Modules:AI:Enabled (legacy Ai:Enabled) |
Modules__AI__Enabled |
Semantic search module | false |
No |
Modules:AI:EmbeddingBaseUrl |
Modules__AI__EmbeddingBaseUrl |
Embedding server base URL | http://localhost:11434 |
No |
Modules:AI:EmbeddingModel |
Modules__AI__EmbeddingModel |
Embedding model name | nomic-embed-text |
No |
Modules:Analytics.Umami:Enabled (legacy Umami:Enabled) |
Modules__Analytics.Umami__Enabled |
Umami analytics module | false |
No |
Modules:Analytics.Umami:BaseUrl |
Modules__Analytics.Umami__BaseUrl |
Umami instance, server to server | empty | No |
Modules:Analytics.Umami:Username |
Modules__Analytics.Umami__Username |
Umami account used to read stats | empty | No |
Modules:Analytics.Umami:Password secret
|
Modules__Analytics.Umami__Password |
Its password | empty | No |
Modules:Analytics.Umami:PublicUrl |
Modules__Analytics.Umami__PublicUrl |
Public URL of the tracking script, for the copy-paste snippet | falls back to BaseUrl
|
No |
The quickstart compose file sets Umami__Enabled and the rest of the Umami keys in their legacy root
form. Those still work and log a deprecation warning naming both sections, because
UmamiAnalyticsModule declares Umami as its legacy section.
It also sets Resend__ApiKey, and that is not a legacy form. ResendEmailModule declares no legacy
section, so there is no Modules:Email.Resend equivalent to move to, and nothing warns about it. The
core reads Resend:ApiKey at the root itself, as the Mail section above says. Leave it where it is.
Never commit these, and never log them:
JWT__Key, Secrets__Key, Mfa__Key, Connectors__Key, Metrics__ScrapeKey,
InitialAdmin__Password, the password inside ConnectionStrings__DefaultConnection or
DATABASE_URL, Resend__ApiKey, Resend__WebhookSecret, Modules__Email.Smtp__Password,
Modules__Files.S3__AccessKey, Modules__Files.S3__SecretKey,
Modules__Analytics.Umami__Password, and every *__ClientSecret or *__AppSecret.
CI runs Gitleaks. A hit is treated as a real incident, because rotating is the only fix once a secret is pushed.
Running
docker-compose.prod.yml,
seven variables use the ${VAR:?...} form, so compose refuses to start without them. That is the
true minimum, and the template with all seven is
.env.prod.example:
DOMAIN_API=api.example.com
ACME_EMAIL=you@example.com
BARAKO_TAG=4.0.1
DB_PASSWORD=<generated>
JWT_KEY=<32+ random characters>
FRONTEND_ORIGINS=https://console.example.com,https://www.example.com
ADMIN_PASSWORD=<your first sign-in>
DOMAIN_API and ACME_EMAIL are Caddy's, not the app's. The hostname has to resolve to the machine
before the first start, because Caddy asks for a certificate immediately and Let's Encrypt validates
over HTTP. BARAKO_TAG is required rather than defaulting to latest so the next
docker compose pull is not an unplanned upgrade. The other four become JWT__Key,
CORS__AllowedOrigins, InitialAdmin__Password and the password inside
ConnectionStrings__DefaultConnection.
Four more variables the file passes through carry a default equal to today's behaviour, so the stack starts without them:
| Variable | Key it sets | Default in the file | Worth setting |
|---|---|---|---|
ALLOWED_HOSTS |
AllowedHosts |
*, any host |
Yes, name the API's host |
APP_BASE_URL |
App__BaseUrl |
empty, so the feed and sitemap answer 503 | Yes |
FEEDS_SITE_URL |
Feeds__SiteUrl |
empty | Only if you publish a feed |
TRUSTED_PROXY_NETWORK |
ForwardedHeaders__KnownNetworks__0 |
172.16.0.0/12 |
Only if your Docker bridge is elsewhere |
ASPNETCORE_ENVIRONMENT=Production, ForwardedHeaders__Enabled=true and
Kubernetes__Enabled=false are written into the file, not variables, and ADMIN_USER defaults to
admin. Nothing to set for those.
TLS in front of it is not optional if browsers are involved, because of the refresh cookie. Caddy is what provides it here.
Method, so you know the shape of the gap:
-
grepacross the whole repository, excludingbin/andobj/and the test project, forGetValue,GetSection,Configuration["..."],GetConnectionString,Configure<T>(...)andBindConfiguration. - A second pass for configuration keys held in constants (
const string ...Key = "...",SectionName,Section), since several areas name their keys once and use the constant everywhere. - Every options class the second pass turned up was read for its property names and default values.
-
appsettings.json,appsettings.Development.json,quickstart/docker-compose.yml,docker-compose.hub.yml,docker-compose.yml,docker-compose.prod.ymland the three.envtemplates were read for the keys an operator actually sets. - Defaults are taken from the source, not from the compose files, where the two differ. The clearest
example is
Seed__DemoContent: the compose file pins it tofalse, the code default follows the environment.
What this will miss:
- A key built at runtime from a string that grep cannot see whole. The ones of this shape that were
found are listed (
Lifecycle:AllowSelfTransition:{name},Feeds:Paths:{type}andFeeds:Titles:{type}), but another could exist. - Settings that are not configuration at all:
SystemSettingdocuments edited through the admin, and anything stored in the database.Kubernetes__Enabledis the one place those two worlds meet in the core. - Settings belonging to modules this page covers only where they read configuration. Accounting, Diagnostics, FeatureFlags, Portability, Pwa and Templates read no configuration keys of their own.
- Standard ASP.NET Core and Serilog keys that barakoCMS does not read itself.
ASPNETCORE_ENVIRONMENTis included because so much of the behaviour above hangs off it, and the Serilog level keys because they ship inappsettings.json, but the full set of framework keys is out of scope.
The three .env templates were read too, and every variable in them names a key documented above:
quickstart/.env.example,
.env.example for
docker-compose.hub.yml, and
.env.prod.example. Read them
for the shape of a working .env, and this page for what each key does.
One thing to know about the quickstart template: a variable in .env reaches the container only if
the compose file maps it to a key, and quickstart/docker-compose.yml does not map all of them.
PUBLIC_API_URL and ADMIN_PORT are documented in the file as names barakoBrew's own quickstart
expects, so they are deliberately unread here. The FILES__S3__* block is not mapped either, so
setting it in that .env does not configure S3 storage; add the keys to the api service's
environment: block yourself, in the Modules:Files.S3:* form this page gives.
Releases
Start here
- Approval by configuration
- Configuring email
- Delivering a client project on barakoCMS
- Deploying barakoCMS on a VM
- Upgrading from 3.x to 4.0
Content
- Content type blueprints
- Public delivery API
- Event-sourced content types
- Image variants
- SEO fields
- URL redirects
Security and access
- Security and compliance posture
- Scanning uploads for malware
- Where the admin keeps your session, and why
Tenancy
Operations