Configuration
Environment-based configuration with security-first design
goilerplate uses environment variables for configuration. All config lives in .env and is loaded into a typed struct.
Quick Start
Copy .env.example to .env:
cp .env.example .env
Edit the values:
# Application
APP_NAME=YourApp
APP_ENV=development # 'development' or 'production'
APP_URL=http://localhost:8090 # Base URL for emails & OAuth
APP_TAGLINE="Your awesome tagline"
[email protected]
PORT=8090
# Security (change these!)
JWT_SECRET=your-secret-key-change-this
# Token Expiry (Go duration format)
JWT_EXPIRY=168h # 7 days
TOKEN_EMAIL_VERIFY_EXPIRY=24h # 24 hours
TOKEN_PASSWORD_RESET_EXPIRY=1h # 1 hour
TOKEN_EMAIL_CHANGE_EXPIRY=24h # 24 hours
# Database
# SQLite (default) - WAL mode enables concurrent readers
DB_DRIVER=sqlite
DB_CONNECTION=./data/app.db?_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)
# PostgreSQL (optional)
#DB_DRIVER=pgx
#DB_CONNECTION=postgres://user:pass@localhost:5432/dbname?sslmode=disable
# OAuth (optional)
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
# Email (resend.com)
[email protected]
RESEND_API_KEY=re_xxxxxxxxxxxxx
RESEND_AUDIENCE_ID=aud_xxxxxxxxxxxxx
# Payment Provider (polar or stripe)
PAYMENT_PROVIDER=polar
# Polar (polar.sh)
POLAR_API_KEY=polar_sk_xxxxxxxxxxxxx
POLAR_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxx
POLAR_PRODUCT_ID_PRO_MONTHLY=prod_xxxxxxxxxxxxx
POLAR_PRODUCT_ID_PRO_YEARLY=prod_xxxxxxxxxxxxx
# Stripe (stripe.com) - Alternative
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxx
STRIPE_PRICE_ID_PRO_MONTHLY=price_xxxxxxxxxxxxx
STRIPE_PRICE_ID_PRO_YEARLY=price_xxxxxxxxxxxxx
# Storage (S3-compatible) - Cloudflare R2 recommended (10GB free)
S3_REGION=auto
S3_BUCKET=my-app-dev
S3_ACCESS_KEY=your_r2_access_key
S3_SECRET_KEY=your_r2_secret_key
S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com # Leave empty for AWS S3
# Analytics (optional)
UMAMI_WEBSITE_ID=
UMAMI_HOST=cloud.umami.is
PLAUSIBLE_DOMAIN=
PLAUSIBLE_HOST=plausible.io
GOOGLE_ANALYTICS_ID=
# Error Tracking (optional)
SENTRY_DSN=
# Logging (optional)
LOG_LEVEL= # debug | info | warn | error (empty = debug in dev, info in prod)
LOG_FORMAT=pretty # pretty (colored, for humans) | json (for log tools)
LOG_HTTP=true # HTTP request logging on/off
See Logging & Observability for the full breakdown of formats and switches.
The Sanitized Config Pattern
Problem: Templates need config values, but shouldn’t access secrets.
Solution: Sanitized() method creates a safe copy:
func (c *Config) Sanitized() *Config {
return &Config{
// Public values ✅
AppName: c.AppName,
AppTagline: c.AppTagline,
SupportEmail: c.SupportEmail,
// Secrets excluded ❌
// JWTSecret: "", // Empty
}
}
How It Works
- Middleware adds sanitized config to context
- Templates get safe values only
- Services get full config via dependency injection
// In templates - safe
{{ cfg := ctxkeys.GetConfig(ctx) }}
<h1>{ cfg.AppName }</h1> // ✅ Works
<p>{ cfg.JWTSecret }</p> // ❌ Empty (secure!)
// In services - full access
authService := NewAuthService(cfg.JWTSecret) // ✅ Full config
Whitelabeling
Zero hardcoded values = easy whitelabeling:
# Company A
APP_NAME="Acme Corp"
[email protected]
# Company B (same codebase!)
APP_NAME="TechStart"
[email protected]
Templates automatically use configured values in:
- Logo text
- Page titles
- Email links
- SEO metadata
- Footer
Environment Detection
Use APP_ENV to handle dev/prod differences:
if cfg.IsProduction() {
// Production settings
secureOnly = true
} else {
// Development settings
debug = true
}
Security
What’s in Context (Public)
- ✅ App name, tagline, URLs
- ✅ Support email
- ✅ Feature flags
What’s NOT in Context (Private)
- ❌ JWT secrets
- ❌ Database credentials
- ❌ API keys
Best Practices
Never log full config:
// ❌ Bad - might expose secrets
log.Printf("Config: %+v", cfg)
// ✅ Good - log specific values
log.Printf("Starting %s on port %s", cfg.AppName, cfg.Port)
Validate required values:
JWTSecret: envRequired("JWT_SECRET"), // No default!
AppName: envString("APP_NAME", "Acme"), // OK default
Never commit .env:
- Use
.env.examplefor documentation - Rotate secrets regularly
- Use different secrets per environment