Configuration
A complete reference for every environment variable in your Grit project. All configuration is done through the .env file at the project root.
Environment Files
Every Grit project includes three environment files:
.env-- Your actual configuration, generated at scaffold time with strong random secrets (JWT, Postgres, Pulse, Sentinel). This file is gitignored and never committed..env.example-- Documented template with all variables and sensible defaults. Committed to git..env.cloud.example-- Template for cloud-only setups (Neon, Upstash, R2/B2) when you do not have Docker.
The Go API loads these variables at startup using godotenv and parses them into a typed Config struct. Environment variables are read once at startup and are available throughout the application.
Application
General application settings that control the server behavior.
# App — General application settingsAPP_NAME=myapp # Application name (used in emails, logs)APP_ENV=development # Environment: development, staging, productionAPP_PORT=8080 # API server portAPP_URL=http://localhost:8080
APP_NAMEProject nameUsed as the application title in email templates, log entries, and the admin panel header. Set to your project name during scaffolding.
APP_ENVdevelopmentControls logging verbosity, GORM Studio visibility, and error detail level. Set to production in deployed environments to disable debug features. Note: Pulse and Sentinel refuse to mount in production if their passwords are still the literal defaults.
APP_PORT8080The port the Go API server listens on. The frontend apps proxy API requests to this port.
APP_URLhttp://localhost:8080The full URL of the API server. Used for generating absolute URLs in emails and file storage signed URLs.
Database
Grit uses PostgreSQL as its primary database, connected through GORM. Rather than a single connection string, the scaffold treats the POSTGRES_* parts as the single source of truth -- both docker-compose.yml and the Go API read them. The Go API builds the connection DSN from these parts automatically when DATABASE_URL is left empty.
# Database (Postgres) — single source of truth# Edit ONLY the POSTGRES_* values below. docker-compose.yml reads them via# ${VAR} substitution and the Go API builds the DSN from these parts when# DATABASE_URL is empty.POSTGRES_USER=gritPOSTGRES_PASSWORD=change-me # generated per-scaffold; MUST change in productionPOSTGRES_DB=myappPOSTGRES_HOST=localhost # "postgres" inside docker-compose.prod.ymlPOSTGRES_PORT=5434 # host port; 5432 inside the docker network# Override the connection string ONLY for external Postgres (Neon, Supabase,# RDS) or SQLite. When set, this wins over the POSTGRES_* parts above.# DATABASE_URL=postgres://user:pass@host:5432/db?sslmode=require# DATABASE_URL=sqlite:./app.db # pure-Go driver, no CGO# DATABASE_URL=sqlite::memory: # gone on restart, great for tests# DATABASE_URL=
POSTGRES_USERgritPostgres role the API connects as. Matches the user created by docker-compose.yml.
POSTGRES_PASSWORD(generated)Password for the Postgres role. grit new generates a strong random value per project so a fresh scaffold runs without editing. Change it in production.
POSTGRES_DBProject nameName of the database to connect to. Defaults to your project name.
POSTGRES_HOSTlocalhostDatabase host. Stays localhost for local Docker; docker-compose.prod.yml overrides it to "postgres" (the service name) for inter-container traffic.
POSTGRES_PORT5434Host port for Postgres. Grit uses 5434 (not the default 5432) to avoid collisions with a Postgres you may already run locally. The container still listens on 5432 inside the Docker network.
DATABASE_URLoptionalOptional full connection string. Leave commented to build the DSN from the POSTGRES_* parts. Set it to point at an external Postgres (postgres://... with sslmode=require) or to use SQLite (sqlite:./app.db or sqlite::memory:). When set, it wins over the POSTGRES_* parts.
JWT Authentication
Grit uses JWT tokens for authentication with separate access and refresh tokens. The access token is short-lived (15 minutes) and the refresh token lasts 7 days.JWT_SECRET is generated at scaffold time.
# JWT — generated at scaffold time. Rotate with: openssl rand -hex 32JWT_SECRET=change-me-in-productionJWT_ACCESS_EXPIRY=15m # Access token lifetimeJWT_REFRESH_EXPIRY=168h # Refresh token lifetime (7 days)
JWT_SECRET(generated)The secret key used to sign and verify JWT tokens. Generated per-scaffold (openssl rand -hex 32). Rotate it in production. Both access and refresh tokens use this same secret.
JWT_ACCESS_EXPIRY15mHow long access tokens are valid. Uses Go duration format: 15m (15 minutes), 1h (1 hour), etc. Keep short for security. The frontend automatically refreshes expired tokens.
JWT_REFRESH_EXPIRY168hHow long refresh tokens are valid. 168h is 7 days. Users must re-login after this period.
OAuth & Social Login
Grit ships with Google and GitHub OAuth2 sign-in. The OAuth API routes are always registered server-side; SOCIAL_AUTH_ENABLED only controls whether the social buttons render on the auth pages. Leave the client IDs and secrets empty to run without social login.
# OAuth2 — Social Login (Google + GitHub)# Google: https://console.cloud.google.com/apis/credentialsGOOGLE_CLIENT_ID= # Google OAuth 2.0 Client IDGOOGLE_CLIENT_SECRET= # Google OAuth 2.0 Client Secret# GitHub: https://github.com/settings/developersGITHUB_CLIENT_ID= # GitHub OAuth App Client IDGITHUB_CLIENT_SECRET= # GitHub OAuth App Client SecretOAUTH_FRONTEND_URL=http://localhost:3001 # Where to redirect after OAuth# Toggle the social buttons + "or continue with" divider on auth pages.# Mirrored to the browser bundle as NEXT_PUBLIC_SOCIAL_AUTH_ENABLED.SOCIAL_AUTH_ENABLED=true
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET(your keys)Google OAuth 2.0 credentials. Create them in the Google Cloud Console under APIs & Services > Credentials.
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET(your keys)GitHub OAuth App credentials. Create an OAuth App under GitHub Settings > Developers.
OAUTH_FRONTEND_URLhttp://localhost:3001The frontend URL the API redirects back to after a successful OAuth exchange. Defaults to the admin panel dev port.
SOCIAL_AUTH_ENABLEDtrueShow or hide the Google/GitHub buttons on auth pages. Set to false to remove them and the "or continue with" divider. The OAuth routes stay registered either way. Mirrored to the browser as NEXT_PUBLIC_SOCIAL_AUTH_ENABLED.
Redis
Redis is used for response caching and background job queues (via Asynq). A single Redis instance handles both use cases.
# Redis — Cache and job queueREDIS_URL=redis://localhost:6380
REDIS_URLredis://localhost:6380Redis connection URL. For local Docker, Grit maps Redis to host port 6380 (not the default 6379) to avoid collisions with a local Redis. For cloud Redis (Upstash), use the rediss:// protocol (with double s) and include the password: rediss://default:password@endpoint:6379.
Frontend & Public URLs
URLs baked into the Next.js web and admin bundles at build time. These tell the clients where to reach the API and the admin panel.
# Public API URL — baked into Next.js bundles at build timeAPI_URL=http://localhost:8080# Admin panel URL — surfaced in the web app's navbar + landing-page dev links.# Set to your production admin origin (e.g. https://admin.example.com) before shipping.NEXT_PUBLIC_ADMIN_URL=http://localhost:3001
API_URLhttp://localhost:8080The public API base URL baked into the Next.js bundles at build time. Point it at your deployed API origin (e.g. https://api.example.com) for production builds.
NEXT_PUBLIC_ADMIN_URLhttp://localhost:3001The admin panel URL surfaced in the web app navbar and landing-page dev links. Defaults to the admin dev port; set to your production admin origin before shipping.
File Storage
Grit supports four S3-compatible storage providers: MinIO (local development), AWS S3, Cloudflare R2, and Backblaze B2. The STORAGE_DRIVER variable controls which provider is active -- only the variables for the active driver need to be set.
# Storage — Which provider to use: minio, s3, r2, b2STORAGE_DRIVER=minio# MinIO — Local S3-compatible storage (default for development)MINIO_ENDPOINT=http://localhost:9002MINIO_ACCESS_KEY=minioadminMINIO_SECRET_KEY=minioadminMINIO_BUCKET=myapp-uploadsMINIO_REGION=us-east-1MINIO_USE_SSL=false# AWS S3 (used when STORAGE_DRIVER=s3)# Leave S3_ENDPOINT empty to use the AWS regional default. S3_ACCESS_KEY /# S3_SECRET_KEY / S3_REGION fall back to AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY# / AWS_REGION, so an IAM role on your EC2 / ECS / Lambda Just Works.S3_ENDPOINT=S3_ACCESS_KEY=S3_SECRET_KEY=S3_BUCKET=S3_REGION=us-east-1# Cloudflare R2 (used when STORAGE_DRIVER=r2)R2_ENDPOINT=R2_ACCESS_KEY= # R2 Access Key IDR2_SECRET_KEY= # R2 Secret Access KeyR2_BUCKET=R2_REGION=auto # Always "auto" for R2# Backblaze B2 (used when STORAGE_DRIVER=b2)B2_ENDPOINT=B2_ACCESS_KEY= # B2 keyIDB2_SECRET_KEY= # B2 applicationKeyB2_BUCKET=B2_REGION=us-west-004 # Must match your bucket region
STORAGE_DRIVERminioWhich storage provider to use. Options: minio (local dev with Docker), s3 (AWS S3), r2 (Cloudflare R2), b2 (Backblaze B2). Only the variables for the active driver need to be set.
MINIO_ENDPOINThttp://localhost:9002MinIO server URL. Grit maps MinIO to host port 9002 (API) and 9003 (web console) to avoid the default 9000/9001 colliding with other services.
MINIO_ACCESS_KEY / MINIO_SECRET_KEYminioadminDefault MinIO credentials. These match the Docker Compose configuration. Change in production if running your own MinIO instance.
S3_ENDPOINT(empty)AWS S3 endpoint. Leave empty to use the AWS regional default (s3.<region>.amazonaws.com) with virtual-hosted-style addressing. Only used when STORAGE_DRIVER=s3.
S3_ACCESS_KEY / S3_SECRET_KEY / S3_REGION(IAM fallback)AWS credentials and region. Fall back to AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_REGION, so leaving them empty lets an attached IAM role supply the credentials automatically.
R2_ENDPOINT(your account)Cloudflare R2 endpoint. Format: https://ACCOUNT_ID.r2.cloudflarestorage.com. Only used when STORAGE_DRIVER=r2.
R2_ACCESS_KEY / R2_SECRET_KEY(your keys)Cloudflare R2 API credentials. Create an API token in the Cloudflare Dashboard under R2 > Manage R2 API Tokens. R2_REGION is always "auto".
B2_ENDPOINT(your region)Backblaze B2 S3-compatible endpoint. Format depends on your bucket region. B2_ACCESS_KEY is the keyID and B2_SECRET_KEY is the applicationKey. Only used when STORAGE_DRIVER=b2.
Grit uses Resend for transactional emails -- welcome emails, password resets, and notifications. In development, emails are caught by Mailhog (accessible at http://localhost:8025).
# Email — Resend integrationRESEND_API_KEY=re_your_api_keyMAIL_FROM=noreply@myapp.dev# Support inbox — every ticket opened in /system/support is emailed here# (when RESEND_API_KEY is set). Leave empty in dev to skip email-out.SUPPORT_EMAIL=
RESEND_API_KEYre_your_api_keyYour Resend API key. Get one at resend.com/api-keys. In development, emails are sent to Mailhog regardless of this key.
MAIL_FROMnoreply@myapp.devThe sender email address for all outgoing emails. Must be a verified domain in your Resend account for production use.
SUPPORT_EMAIL(empty)Destination inbox for support tickets opened in /system/support. Tickets are emailed here when RESEND_API_KEY is set. Leave empty in dev to skip sending.
CORS
Cross-Origin Resource Sharing configuration. The Go API needs to know which frontend origins are allowed to make requests. The Wails desktop webview does not need an entry -- its origin is matched by host (wails.localhost) on any port.
# CORS — Allowed frontend origins (comma-separated)CORS_ORIGINS=http://localhost:3000,http://localhost:3001
CORS_ORIGINShttp://localhost:3000,http://localhost:3001Comma-separated list of allowed origins. In development, port 3000 is the web app and port 3001 is the admin panel. In production, set this to your actual domain names (e.g., https://myapp.com,https://admin.myapp.com).
GORM Studio
The embedded visual database browser. Accessible at /studio on the API server, behind its own basic-auth login.
# GORM Studio — Visual database browserGORM_STUDIO_ENABLED=trueGORM_STUDIO_USERNAME=admin # Login username for the Studio UIGORM_STUDIO_PASSWORD=studio # Login password for the Studio UI
GORM_STUDIO_ENABLEDtrueEnable or disable GORM Studio. Set to true in development for visual database browsing. Set to false in production to disable the browser and prevent unauthorized access to your data.
GORM_STUDIO_USERNAMEadminLogin username for the Studio UI.
GORM_STUDIO_PASSWORDstudioLogin password for the Studio UI. Change it if you keep Studio enabled outside local development.
AI Integration
Grit ships with built-in AI support via Vercel AI Gateway (one key, hundreds of models). The AI service provides text completion and streaming endpoints.
# AI — Vercel AI Gateway (one key, hundreds of models)AI_GATEWAY_API_KEY= # Get from vercel.com/ai-gatewayAI_GATEWAY_MODEL=anthropic/claude-sonnet-4-6 # provider/model formatAI_GATEWAY_URL=https://ai-gateway.vercel.sh/v1
AI_GATEWAY_API_KEY(your key)Your Vercel AI Gateway API key. Get one at vercel.com/ai-gateway. A single key gives you access to all providers (Anthropic, OpenAI, Google, and more). Leave empty if you do not use AI features.
AI_GATEWAY_MODELanthropic/claude-sonnet-4-6The model to use, in provider/model format. Examples: anthropic/claude-sonnet-4-6, openai/gpt-5.4, google/gemini-2.5-pro.
AI_GATEWAY_URLhttps://ai-gateway.vercel.sh/v1The Vercel AI Gateway endpoint URL. This is the same for all providers and models. You should not need to change this.
Two-Factor Authentication
Grit supports TOTP-based two-factor authentication. The issuer name appears in authenticator apps (e.g., Google Authenticator, Authy) alongside the user's account.
# Two-Factor Authentication (TOTP)TOTP_ISSUER=myapp
TOTP_ISSUERmyappThe issuer name displayed in authenticator apps when users set up 2FA. Set this to your application or company name.
Observability (Pulse)
Pulse is Grit's built-in performance monitoring, request-tracing, and error-tracking dashboard. Its password is generated at scaffold time -- Pulse refuses to mount in APP_ENV=production while the password is still the literal default pulse.
# Observability — Pulse performance monitoring dashboardPULSE_ENABLED=true # Set to "false" to disable Pulse entirelyPULSE_USERNAME=admin # Dashboard login usernamePULSE_PASSWORD=pulse # Generated per-scaffold. Rotate: openssl rand -hex 16
PULSE_ENABLEDtrueTurn the Pulse dashboard on or off. Set to false to disable Pulse entirely.
PULSE_USERNAMEadminLogin username for the Pulse dashboard.
PULSE_PASSWORD(generated)Login password for the Pulse dashboard. Generated per-scaffold so the production gate is satisfied out of the box. Rotate with openssl rand -hex 16.
Security (Sentinel)
Sentinel is Grit's built-in WAF, rate limiter, and threat-detection layer with its own dashboard. Like Pulse, it refuses to mount in production while its credentials are still the defaults. Both SENTINEL_PASSWORD andSENTINEL_SECRET_KEY are generated at scaffold time.
# Security — Sentinel WAF, rate limiting, threat detectionSENTINEL_ENABLED=true # Set to "false" to disable Sentinel entirelySENTINEL_USERNAME=admin # Dashboard login usernameSENTINEL_PASSWORD=sentinel # Generated per-scaffold. Rotate: openssl rand -hex 16SENTINEL_SECRET_KEY=change-me # Generated per-scaffold (>=32 bytes). Rotate: openssl rand -hex 32
SENTINEL_ENABLEDtrueTurn Sentinel on or off. Set to false to disable the WAF, rate limiting, and threat detection entirely.
SENTINEL_USERNAMEadminLogin username for the Sentinel dashboard.
SENTINEL_PASSWORD(generated)Login password for the Sentinel dashboard. Generated per-scaffold. Rotate with openssl rand -hex 16.
SENTINEL_SECRET_KEY(generated)Secret used to sign the Sentinel dashboard JWT sessions. Needs at least 32 bytes of entropy; generated per-scaffold. Rotate with openssl rand -hex 32.
Theme
Picks the visual identity for the auth pages and dashboard. The value is mirrored into NEXT_PUBLIC_THEME for the web and admin clients innext.config, so server components render the right theme without a flash of unstyled content.
# Theme — visual identity for auth pages + dashboard# atlas — split-screen, team/organisation, Inter (default)# aurora — centered Clerk-style, consumer SaaS, Geist# pulse — split-screen with carousel, ecommerce/brand, Onest + DM SerifTHEME=atlas
THEMEatlasChosen with --theme at scaffold time (defaults to atlas). Options: atlas (split-screen, team/organisation, Inter), aurora (centered Clerk-style, consumer SaaS, Geist), and pulse (split-screen with carousel, ecommerce/brand, Onest + DM Serif). Mirrored to the clients as NEXT_PUBLIC_THEME.
Local Service Ports
The services started by Docker Compose and the dev servers each bind their own host port. Grit shifts the infrastructure ports off their defaults (Postgres, Redis, MinIO) to avoid colliding with services you may already run locally.
| Service | Dev URL | Host Port |
|---|---|---|
| Go API | http://localhost:8080 | 8080 |
| GORM Studio | http://localhost:8080/studio | 8080 |
| Web App (Next.js) | http://localhost:3000 | 3000 |
| Admin Panel (Next.js) | http://localhost:3001 | 3001 |
| PostgreSQL | localhost:5434 | 5434 |
| Redis | localhost:6380 | 6380 |
| MinIO (API) | http://localhost:9002 | 9002 |
| MinIO Console | http://localhost:9003 | 9003 |
| Mailhog | http://localhost:8025 | 8025 |
Complete .env Reference
Here is every environment variable in a single block for quick copy-paste:
# AppAPP_NAME=myappAPP_ENV=developmentAPP_PORT=8080APP_URL=http://localhost:8080# Database (Postgres) — edit these; DATABASE_URL is optionalPOSTGRES_USER=gritPOSTGRES_PASSWORD=change-mePOSTGRES_DB=myappPOSTGRES_HOST=localhostPOSTGRES_PORT=5434# DATABASE_URL=postgres://user:pass@host:5432/db?sslmode=require# DATABASE_URL=sqlite:./app.db# DATABASE_URL=sqlite::memory:# JWTJWT_SECRET=change-me-in-productionJWT_ACCESS_EXPIRY=15mJWT_REFRESH_EXPIRY=168h# OAuth2 — Social Login (Google + GitHub)GOOGLE_CLIENT_ID=GOOGLE_CLIENT_SECRET=GITHUB_CLIENT_ID=GITHUB_CLIENT_SECRET=OAUTH_FRONTEND_URL=http://localhost:3001SOCIAL_AUTH_ENABLED=true# RedisREDIS_URL=redis://localhost:6380# Frontend & Public URLsAPI_URL=http://localhost:8080NEXT_PUBLIC_ADMIN_URL=http://localhost:3001# Storage — minio, s3, r2, b2STORAGE_DRIVER=minio# MinIO (local dev)MINIO_ENDPOINT=http://localhost:9002MINIO_ACCESS_KEY=minioadminMINIO_SECRET_KEY=minioadminMINIO_BUCKET=myapp-uploadsMINIO_REGION=us-east-1MINIO_USE_SSL=false# AWS S3S3_ENDPOINT=S3_ACCESS_KEY=S3_SECRET_KEY=S3_BUCKET=S3_REGION=us-east-1# Cloudflare R2R2_ENDPOINT=R2_ACCESS_KEY=R2_SECRET_KEY=R2_BUCKET=R2_REGION=auto# Backblaze B2B2_ENDPOINT=B2_ACCESS_KEY=B2_SECRET_KEY=B2_BUCKET=B2_REGION=us-west-004RESEND_API_KEY=re_your_api_keyMAIL_FROM=noreply@myapp.devSUPPORT_EMAIL=# CORSCORS_ORIGINS=http://localhost:3000,http://localhost:3001# GORM StudioGORM_STUDIO_ENABLED=trueGORM_STUDIO_USERNAME=adminGORM_STUDIO_PASSWORD=studio# AI (Vercel AI Gateway)AI_GATEWAY_API_KEY=AI_GATEWAY_MODEL=anthropic/claude-sonnet-4-6AI_GATEWAY_URL=https://ai-gateway.vercel.sh/v1# Two-Factor Authentication (TOTP)TOTP_ISSUER=myapp# Observability (Pulse)PULSE_ENABLED=truePULSE_USERNAME=adminPULSE_PASSWORD=pulse# Security (Sentinel)SENTINEL_ENABLED=trueSENTINEL_USERNAME=adminSENTINEL_PASSWORD=sentinelSENTINEL_SECRET_KEY=change-me# Theme — atlas, aurora, pulseTHEME=atlas
Production Checklist
Before deploying to production, make sure you have addressed these configuration items:
- Change
JWT_SECRETto a strong random string (at least 32 characters) - Set
APP_ENV=productionto disable debug logging and GORM Studio - Set
GORM_STUDIO_ENABLED=false(or change its username/password) to lock down the database browser - Change
POSTGRES_PASSWORD(or setDATABASE_URLto your managed Postgres withsslmode=require) - Update
CORS_ORIGINSto your production domain names - Set
STORAGE_DRIVERtos3,r2, orb2and configure the cloud credentials - Set
RESEND_API_KEYwith your production API key, verify your sender domain, and setSUPPORT_EMAIL - Update
REDIS_URLto your production Redis instance - Set
API_URLandNEXT_PUBLIC_ADMIN_URLto your production origins - Rotate
PULSE_PASSWORD,SENTINEL_PASSWORD, andSENTINEL_SECRET_KEYoff the literal defaults (they gate mounting in production) - Fill in OAuth credentials if you use social login, or set
SOCIAL_AUTH_ENABLED=false - Set
APP_URLto your production API domain
