Configuration
Kit packages never read env vars on their own: each package exports a Config struct, which you compose in your project's internal/platform/config/config.go and load with envconf.MustLoad[config.Config](). See 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 kit but by the composing struct's envPrefix tag: the DB_, S3_, MAIL_ etc. prefixes 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 REDIS_/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_"`
}
server.Config: no prefix
Lifecycle and limits of the HTTP server, regardless of engine. See Server.
| Variable | Default | Meaning |
|---|---|---|
LISTEN_ADDR | :3000 | the address the engine (Fiber or 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 (separate only on the net/http-based chi; Fiber folds it into READ_TIMEOUT), 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 |
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 |
telemetry.Config: nested, no prefix
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 the observability overview. All variables are read under standard names at the top level, regardless of nesting depth.
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 the kit the same way as any other OTel-instrumented application.logx.Config: the default slog logger
See 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 |
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 |
dbx.Config: envPrefix:"DB_"
PostgreSQL connection, see the database 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 REDIS_ADDR default is localhost:6379, while the generated .env.example ships redis: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 |
auth.Config: envPrefix:"JWT_"
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.s3.Config: envPrefix:"S3_"
S3-compatible object storage, see Storage.
| Variable | Default | Meaning |
|---|---|---|
S3_ENDPOINT | empty | empty → AWS S3; for MinIO/RustFS the service address |
S3_REGION | us-east-1 | |
S3_BUCKET | required | |
S3_ACCESS_KEY / S3_SECRET_KEY | empty | both empty → default AWS credential chain (IAM role etc.) |
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) |
smtp.Config: envPrefix:"MAIL_"
SMTP sending and default sender, see Email.
| 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 |
queue.Config: envPrefix:"REDIS_"
The queue's (asynq's) Redis connection, see Queue.
| Variable | Default | Meaning |
|---|---|---|
REDIS_ADDR | localhost:6379 | Redis host:port |
REDIS_PASSWORD | empty | empty → no auth |
REDIS_DB | 0 | Redis logical database |
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 |
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 Redis, set it to a name unique to the app when multiple applications/environments share one Redis |
worker.Config: the cmd/worker binary
Its own WORKER_-prefixed fields, plus embedded: queue.Config (REDIS_), scheduler.Config (SCHEDULER_) and telemetry.Config (standard OTEL_*/LOG_*/SENTRY_*, see above). 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) |
The compose dev stack's env variables (not a kit Config field)
These are generated into .env.example by new project; they don't belong to any kit 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.