Every runtime knob exposed by pkg/app.Config is settable through an
environment variable. This page is the single source of truth for
operators: defaults, allowed values, and the corresponding factor.
Heroku coined the 12 Factor App doctrine. Factors III (config), IX (disposability), XI (logs), XII (admin processes) are the ones we touch directly here.
| Variable | Default | Factor | Notes |
|---|---|---|---|
APP_BIND |
127.0.0.1:8080 |
III | listening address |
APP_DATA_SOURCE |
fixture |
IV | example DSN, app-defined |
APP_STATIC_DIR |
internal/site/web/static |
III | static files root |
APP_DEFAULT_LOCALE |
en |
III | i18n fallback |
APP_AVAILABLE_LOCALES |
en,ru |
III | comma-separated |
| Variable | Default | Factor | Notes |
|---|---|---|---|
APP_HTTP_READ_TIMEOUT |
5s |
IX | full request read |
APP_HTTP_READ_HEADER_TIMEOUT |
2s |
IX | Slowloris defence |
APP_HTTP_WRITE_TIMEOUT |
10s |
IX | response write |
APP_HTTP_IDLE_TIMEOUT |
120s |
IX | keep-alive idle |
APP_HTTP_MAX_HEADER_BYTES |
1MB |
IX | header size cap |
APP_HTTP_SHUTDOWN_TIMEOUT |
5s |
IX | graceful drain |
Garbage / negative values fall back to the default. Durations use
Go's time.ParseDuration syntax (500ms, 2s, 1m30s).
| Variable | Default | Factor | Notes |
|---|---|---|---|
OIDC_ISSUER |
— | III | OpenID Connect issuer URL |
OIDC_CLIENT_ID |
— | III | OIDC client ID |
OIDC_CLIENT_SECRET |
— | III | OIDC client secret |
OIDC_REDIRECT_URI |
— | III | post-callback redirect |
SESSION_KEY |
— | III | HMAC key for cookie sessions |
| Variable | Default | Factor | Notes |
|---|---|---|---|
APP_SECURITY_HSTS |
false |
III | enable only when the full site is HTTPS |
APP_SECURITY_FRAME_OPTIONS |
DENY |
III | set empty to disable |
APP_SECURITY_CSP |
— | III | app-owned Content-Security-Policy |
APP_SECURITY_PERMISSIONS |
geolocation=(), microphone=(), camera=() |
III | browser permission policy |
APP_SECURITY_MAX_BODY_BYTES |
1048576 |
III | request body cap |
APP_SECURITY_RATE_PER_IP |
50 |
III | rate-limit tokens per second |
APP_SECURITY_RATE_BURST |
100 |
III | per-IP token bucket burst |
APP_SECURITY_TRUST_PROXY |
false |
III | opt in only behind a trusted header-scrubbing proxy |
APP_SECURITY_BLOCK_EMPTY_UA |
true |
III | reject empty User-Agent |
APP_SECURITY_ENABLED |
true |
III | master switch for security middleware |
| Variable | Default | Factor | Notes |
|---|---|---|---|
LOG_LEVEL |
info |
XI | debug, info, warn, error |
LOG_FORMAT |
text |
XI | text or json |
HEALTH_LIVE_PATH |
— | IX | empty disables liveness probe |
HEALTH_READY_PATH |
— | IX | empty disables readiness probe |
METRICS_PATH |
— | XI | empty disables /metrics |
OTEL_SERVICE_NAME |
— | XI | passed to optional fastygo/otel adapter |
OTEL_EXPORTER_OTLP_ENDPOINT |
— | XI | passed to optional fastygo/otel adapter |
pkg/app does not install a slog handler nor configure an OTel
exporter automatically. The composition root reads these fields and
wires the appropriate adapter. This keeps the framework's go.mod
free of tracing SDKs.
A 12-factor-friendly main looks like this:
func main() {
cfg, err := app.LoadConfig()
if err != nil { log.Fatal(err) }
// 1. logger from LOG_LEVEL / LOG_FORMAT
slog.SetDefault(buildLogger(cfg.LogLevel, cfg.LogFormat))
// 2. tracer from OTEL_* (optional, separate dep)
tracer, shutdown := otel.New(cfg.OTelServiceName, cfg.OTelExporterEndpoint)
defer shutdown(context.Background())
a := app.New(cfg).
WithTracer(tracer).
WithMetricsEndpoint(cfg.MetricsPath).
WithHealthEndpoints(cfg.HealthLivePath, cfg.HealthReadyPath).
Use(myFeature{}).
Build()
if err := a.Run(); err != nil { log.Fatal(err) }
}Empty paths (METRICS_PATH="") cleanly disable the corresponding
endpoint — the builder treats empty strings as "feature off".
Per Factor IX (Disposability), App.Run traps SIGINT and SIGTERM,
calls http.Server.Shutdown with HTTPShutdownTimeout, then waits
for WorkerService.Stop so background tasks finish their current
iteration before the process exits. Containers can rely on a clean
exit 0 within HTTPShutdownTimeout of SIGTERM.