Configuration
No package, framework or standalone module, reads env vars on its own: each one exports a Config struct, which you compose in your project's internal/platform/config/config.go and load with envconf.MustLoad[config.Config](). See envconf and the configuration concept. In development a .env file is read; real environment variables always win.
GPSYSTEM_* namespace. Variable name prefixes are determined not by the framework or a module but by the composing struct's envPrefix tag: the DB_, S3_, MAIL_ etc. prefixes below are conventions of the generated project. The same struct mounts under a different prefix too: envconf.LoadPrefixed[dbx.Config]("ANALYTICS_DB_") maps the same struct onto a different variable set.A generated project's config composes like this:
type Config struct {
Server server.Config // no prefix: LISTEN_ADDR, ...
DB dbx.Config `envPrefix:"DB_"`
Worker worker.Config // WORKER_* + embedded VALKEY_/SCHEDULER_
Outbox outbox.Config `envPrefix:"OUTBOX_"`
// added by hand when needed:
Storage s3.Config `envPrefix:"S3_"`
Mail smtp.Config `envPrefix:"MAIL_"`
JWT auth.Config `envPrefix:"JWT_"`
}
The sections below follow the module map's order: the framework's own chassis configs first, then one section per standalone module (skipping the handful with no env-configurable state at all).
The framework's chassis
server.Config: no prefix
github.com/gp-system/framework/server. Lifecycle and limits of the HTTP server. See Server.
| Variable | Default | Meaning |
|---|---|---|
LISTEN_ADDR | :3000 | the address chi listens on |
SHUTDOWN_TIMEOUT | 10s | graceful shutdown budget: after SIGINT/SIGTERM, in-flight requests get this long |
READ_HEADER_TIMEOUT | 5s | limit for receiving the request headers, the primary Slowloris defense |
READ_TIMEOUT | 30s | limit for reading the whole request (headers + body) |
WRITE_TIMEOUT | 0s | limit for writing the response; disabled by default because it would also cut off streaming/large downloads. Enable it when all your endpoints are short-lived |
IDLE_TIMEOUT | 120s | how long an idle keep-alive connection stays open |
CORS_ORIGINS | * | comma-separated allowed origins |
CORS_METHODS | GET,POST,PUT,PATCH,DELETE,OPTIONS | the methods CORS preflights allow |
CORS_HEADERS | Content-Type,Authorization | the request headers CORS preflights allow |
CORS_ALLOW_CREDENTIALS | false | allows cookies/credentials on cross-origin requests; invalid together with wildcard origins |
HTTP_TRUST_PROXY | false | rewrites RemoteAddr from X-Real-Ip/X-Forwarded-For; enable only behind a proxy that overwrites those headers |
HTTP_ACCESS_LOG | false | the structured access log, one record per request; telemetry dev mode always logs regardless |
HTTP_REQUEST_TIMEOUT | 0s | per-request context deadline; disabled by default because it would also cap streaming responses |
BODY_LIMIT | 4194304 | max request body size in bytes (4 MiB); a value ≤ 0 also means the default, the cap can never be disabled by accident |
HTTP_EXPOSE_INTERNAL_ERRORS | false | 5xx responses include the underlying error message, stack, and wrap chain (development only); deliberately decoupled from OTEL_DEV_MODE |
worker.Config: WORKER_*
github.com/gp-system/framework/worker. The background-processing chassis: asynq server + outbox relay + scheduler in one binary. See Worker.
| Variable | Default | Meaning |
|---|---|---|
WORKER_CONCURRENCY | 10 | tasks processed simultaneously |
WORKER_QUEUES | default:1 | queue→weight map, e.g. critical:6,default:3,low:1 (higher-weight queues are served more often) |
WORKER_STRICT_PRIORITY | false | serve higher-weight queues to exhaustion before lower ones |
WORKER_SHUTDOWN_TIMEOUT | 30s | graceful drain; unfinished tasks are requeued by asynq and redelivered (at-least-once) |
WORKER_DEFAULT_MAX_RETRY | 25 | retry budget for listener/job tasks that don't set their own (queue.MaxRetry) |
worker.Config also embeds queue.Config (VALKEY_*), scheduler.Config (SCHEDULER_*) and telemetry.Config (standard OTEL_*/LOG_*/SENTRY_*).
realtime.Config: REALTIME_*
github.com/gp-system/framework/realtime. The WebSocket/SSE gateway. See Realtime.
| Variable | Default | Meaning |
|---|---|---|
REALTIME_LISTEN_ADDR | :3000 | the gateway's container-internal listen address |
REALTIME_DRAIN_TIMEOUT | 20s | graceful shutdown: every held connection is disconnected with a reconnect-eligible code, then closers run |
REALTIME_READ_HEADER_TIMEOUT | 5s | Slowloris defense on the connect handshake |
REALTIME_MAX_CONNECTIONS | 0 | caps concurrent streams held by this instance; 0 means unlimited |
CORS_ORIGINS | * | shared with server.Config; besides the CORS headers, it also drives origin checking on all three transports (websocket, http_stream, sse), see Realtime: Origin checking |
realtime.Config also embeds broadcast.Config (envPrefix:"REALTIME_", so its one variable becomes REALTIME_CHANNEL_PREFIX), queue.Config (VALKEY_*, the same Valkey the centrifuge broker uses), auth.Config (JWT_*, authenticates the connect handshake) and telemetry.Config (standard names, no prefix).
dbx: envPrefix:"DB_"
github.com/gp-system/dbx. PostgreSQL connection, see dbx: Overview.
| Variable | Default | Meaning |
|---|---|---|
DB_HOST | localhost | |
DB_PORT | 5432 | |
DB_USER | required | |
DB_PASSWORD | required | |
DB_NAME | required | database name |
DB_SSLMODE | disable | |
DB_MAX_CONNS | 10 | maximum connections of the pgx pool |
dbx.Config's package-level default is DB_HOST=localhost, but a generated project's .env.example ships DB_HOST=postgres: the app runs inside the compose dev stack, and postgres is the compose service's DNS name. Likewise queue.Config's VALKEY_ADDR default is localhost:6379, while the generated .env.example ships valkey:6379.Named connections
Each named connection (add db <module> <name>) adds another dbx.Config under DB_<NAME>_*, the same variables and defaults as above, with the connection's (uppercased) name spliced into the prefix. add db news analytics adds:
| Variable | Default | Meaning |
|---|---|---|
DB_ANALYTICS_HOST | localhost | |
DB_ANALYTICS_PORT | 5432 | |
DB_ANALYTICS_USER | required | |
DB_ANALYTICS_PASSWORD | required | |
DB_ANALYTICS_NAME | required | database name |
DB_ANALYTICS_SSLMODE | disable | |
DB_ANALYTICS_MAX_CONNS | 10 | maximum connections of this connection's own pgx pool |
errs, httperr and paginate have no Config struct: errs and paginate carry no configurable state at all, and httperr reads nothing from the environment either (its HTTP_EXPOSE_INTERNAL_ERRORS behavior lives in server.Config, above). envconf is the loader itself, not something that's loaded; see envconf: Overview for its own .env-file and tag conventions.
queue.Config: envPrefix:"VALKEY_"
github.com/gp-system/queue. The Valkey connection shared by asynq and every module built on it. See Queue.
| Variable | Default | Meaning |
|---|---|---|
VALKEY_ADDR | localhost:6379 | Valkey host:port |
VALKEY_PASSWORD | empty | empty → no auth |
VALKEY_DB | 0 | Valkey logical database |
events
github.com/gp-system/events. The event/listener core has no Config of its own; its two subpackages do.
events/outbox.Config: envPrefix:"OUTBOX_"
The relay's behavior, see Outbox.
| Variable | Default | Meaning |
|---|---|---|
OUTBOX_POLL_INTERVAL | 1s | sleep between polls when a batch is not full |
OUTBOX_BATCH_SIZE | 100 | rows claimed per poll (FOR UPDATE SKIP LOCKED) |
OUTBOX_RETENTION | 168h | how long published rows are kept before cleanup |
OUTBOX_CLEANUP_INTERVAL | 1h | how often published rows past retention are purged |
OUTBOX_TASK_RETENTION | 24h | asynq retention / TaskID dedup window on published tasks |
events/scheduler.Config: envPrefix:"SCHEDULER_"
The scheduled-job runner, see Scheduler.
| Variable | Default | Meaning |
|---|---|---|
SCHEDULER_TIMEZONE | UTC | IANA timezone cron specs are evaluated in (e.g. Europe/Budapest) |
SCHEDULER_LEASE_TTL | 15s | leader lease TTL; the leader renews every TTL/3, the longest a lost leader can keep firing before another replica takes over |
SCHEDULER_NAMESPACE | gpsystem | namespace of the leader-election key in Valkey; this default is the literal string "gpsystem", so set it to a name unique to your app when multiple applications or environments share one Valkey |
auth.Config: envPrefix:"JWT_"
github.com/gp-system/auth. JWT issuing and verification, see Authentication.
| Variable | Default | Meaning |
|---|---|---|
JWT_SECRET | required | signing key, distinct per service/environment |
JWT_ACCESS_TOKEN_TTL | 15m | access token lifetime |
JWT_REFRESH_TOKEN_TTL | 168h | refresh token lifetime |
JWT_ISSUER | empty | empty → the iss check is disabled; when set, Parse requires a matching iss |
JWT_AUDIENCE | empty | empty → the aud check is disabled; when set, Parse requires the token's aud claim to contain it |
JWT_ISSUER and JWT_AUDIENCE (and use a distinct secret per service) so a token minted for another service or environment cannot be replayed.auth/rbac and auth/policy have no Config of their own: role/permission checks and per-request policies run entirely on data already in the token or the request, nothing further to configure.
mail/smtp.Config: envPrefix:"MAIL_"
github.com/gp-system/mail/smtp. SMTP sending and default sender, see Mail: Overview.
| Variable | Default | Meaning |
|---|---|---|
MAIL_HOST | required | SMTP server address |
MAIL_PORT | 587 | |
MAIL_USERNAME / MAIL_PASSWORD | empty | both empty → no auth (Mailpit, internal relays) |
MAIL_AUTH | auto | auth mechanism: auto (discover) | plain | login | cram-md5 | none |
MAIL_TLS | starttls | starttls (mandatory STARTTLS) | starttls-opportunistic (dev, TLS-less server) | tls (implicit TLS, port 465) | none |
MAIL_FROM_ADDRESS | required | default sender address |
MAIL_FROM_NAME | empty | default sender display name |
MAIL_TIMEOUT | 15s | dial/send limit per send |
mail itself and mail/mjml have no Config: the Mailer interface and MJML rendering take no environment-sourced settings.
notify
github.com/gp-system/notify. The Hub/Sender core has no Config; notify/database has none either (it takes a *pg.DB/*bun.DB directly, no variables of its own). notify/broadcast does:
notify/broadcast.Config: CHANNEL_PREFIX
See Broadcast.
| Variable | Default | Meaning |
|---|---|---|
CHANNEL_PREFIX | rt | prefix every centrifuge channel name is built from |
broadcast.Config carries no fixed env prefix of its own (unlike dbx.Config or smtp.Config): the embedding struct's envPrefix tag decides the real variable name. The framework's own realtime.Config embeds it as Broadcast broadcast.Config `envPrefix:"REALTIME_"`, so in a generated project this variable is actually REALTIME_CHANNEL_PREFIX, not the bare CHANNEL_PREFIX shown above.
storage
github.com/gp-system/storage. The Driver/Disk/Manager core and storage/local carry no fixed env prefix either: storage/local.Config has ROOT (required, the local root directory), PUBLIC_BASE_URL, BASE_URL and SECRET (for signed URLs), and it's the caller's envPrefix choice, same as broadcast.Config above, that decides the real variable names in a project.
minio-storage-driver (s3.Config): envPrefix:"S3_"
github.com/gp-system/minio-storage-driver, storage's S3-compatible driver as a standalone module (the declared package name is s3; its own go.mod pulls in the minio-go client). See S3.
| Variable | Default | Meaning |
|---|---|---|
S3_ENDPOINT | required | the S3-compatible service address (MinIO, RustFS, AWS S3, ...) |
S3_REGION | us-east-1 | |
S3_BUCKET | required | |
S3_ACCESS_KEY / S3_SECRET_KEY | empty | credentials for the endpoint |
S3_USE_PATH_STYLE | false | /bucket/key addressing instead of virtual hosts, true for most self-hosted S3 |
S3_PUBLIC_BASE_URL | empty | base for URL() (CDN or public bucket endpoint) |
telemetry.Config: nested, no prefix
github.com/gp-system/telemetry. server.Config.Telemetry and worker.Config.Telemetry both carry the telemetry.Config{Otel, Log, Sentry} trio; telemetry.Setup composes the three subsystems in one call. See Telemetry: Overview. All variables are read under standard names at the top level, regardless of nesting depth.
telemetry/otelx.Config: OTel bootstrap
See OpenTelemetry.
| Variable | Default | Meaning |
|---|---|---|
OTEL_SERVICE_NAME | required | required unless dev mode: service.name on all telemetry |
OTEL_SERVICE_VERSION | dev | service.version resource attribute |
OTEL_DEV_MODE | false | true → no OTel exporters are created (unless Sentry tracing supplies one) |
OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_TRACES_EXPORTER etc. variables apply (via OTel's autoexport package); deployments configure gpsystem the same way as any other OTel-instrumented application.logx.Config: the default slog logger
Provided by the standalone logx module, not telemetry itself; telemetry.Config consumes it as its embedded Log field. See logx: Overview and Telemetry: Logging.
| Variable | Default | Meaning |
|---|---|---|
LOG_LEVEL | INFO | minimum slog level (DEBUG/INFO/WARN/ERROR) |
LOG_STDOUT | false | in prod mode, logs are also written to stdout (besides the OTLP bridge); leave it off when a collector scrapes container output too, or logs are ingested twice |
LOG_FORMAT | empty | console format: empty/auto → text (dev) or JSON (prod); monolog → PHP/Monolog-style lines with full-path stack traces (logx/monolog); any other value fails at startup |
telemetry/sentryx.Config: the optional Sentry integration
See Sentry.
| Variable | Default | Meaning |
|---|---|---|
SENTRY_DSN | empty | empty → the integration is fully disabled, every hook is a no-op |
SENTRY_ENVIRONMENT | development in dev mode, production otherwise | environment tag on Sentry events |
SENTRY_TRACES | true | OTel spans are also shipped to Sentry (as an additional exporter); false when a collector forwards them, sampling is governed by OTEL_TRACES_SAMPLER, not a separate Sentry rate |
SENTRY_DEBUG | false | the Sentry SDK's own debug logging to stderr |
The compose dev stack's env variables (not a Config field)
These are generated into .env.example by new project; they don't belong to any package's Config struct, they drive compose.yml/Dockerfile substitution and Traefik routing.
| Variable | Default | Meaning |
|---|---|---|
USER_ID / GROUP_ID | 1000 | the dev containers run as this host UID/GID (id -u/id -g), so files written into the bind-mounted source stay yours, not root's |
APP_HOST | <project>.localhost | the module services' Traefik Host() rule |
MAILPIT_HOST | mailpit.<project>.localhost | the mailpit UI's Traefik host |
RUSTFS_S3_HOST | rustfs.<project>.localhost | the rustfs S3 API's Traefik host |
RUSTFS_CONSOLE_HOST | rustfs-console.<project>.localhost | the rustfs web console's Traefik host |
The shipped MAIL_*/S3_* lines (MAIL_HOST=mailpit, S3_ENDPOINT=http://rustfs:9000, ...) match the smtp.Config/s3.Config prefixes above: the mailpit/rustfs compose services boot with these, but wiring them (adding the fields to the project's config.go) stays opt-in.