Skip to content

Environment Variables

Every env var read by the backend or frontend. Sources: backend/env.example.local, scripts/pull-secrets.sh (authoritative list of prod secrets pulled from GCP Secret Manager), backend/config/config.go, frontend/eas.json, and direct grep of frontend/utils/.

Backend

The backend uses a two-layer config: config.{env}.yaml for non-sensitive defaults, .env.{env} for secrets and overrides. Env vars listed here override the matching YAML value. See Configuration for layer precedence.

Variable Required? Default Purpose
ENV no local Selects which config.{env}.yaml and .env.{env} to load. Values: local, dev, staging, prod (production is normalized to prod).
DB_HOST yes from YAML Postgres host.
DB_PORT yes from YAML Postgres port.
DB_USER yes from YAML Postgres user.
DB_PASSWORD yes Postgres password.
DB_NAME yes from YAML Postgres database name.
DB_SSLMODE yes from YAML Postgres SSL mode (disable locally, require in prod).
REDIS_HOST yes from YAML Redis host.
REDIS_PORT yes from YAML Redis port.
REDIS_PASSWORD no (local) / yes (prod) Redis password. Blank for local docker-compose.
JWT_SECRET yes HMAC secret for signing JWTs. Generate with openssl rand -base64 32.
ENCRYPTION_KEY yes Symmetric key for encrypting provider config (OAuth tokens, etc.) at rest. 32 chars recommended.
AWS_ACCESS_KEY_ID yes S3/MinIO access key for object storage.
AWS_SECRET_ACCESS_KEY yes S3/MinIO secret key.
S3_ENDPOINT no Override endpoint. Set to http://localhost:9000 for local MinIO; leave unset for AWS S3.
STRIPE_SECRET_KEY for payments Stripe API key. Use sk_test_… locally.
STRIPE_WEBHOOK_SECRET for payments Verifies the signature on Stripe webhook callbacks (whsec_…).
GOOGLE_CLIENT_ID for Google OAuth OAuth client ID for Google Sign-In.
GOOGLE_CLIENT_SECRET for Google OAuth OAuth client secret.
GOOGLE_PLACES_API_KEY for Places Google Places API key (autocomplete, place details).
LINE_CLIENT_ID for LINE login LINE Login channel ID.
LINE_CLIENT_SECRET for LINE login LINE Login channel secret.
APPLE_CLIENT_ID for Apple SIWA (iOS) iOS bundle ID, e.g. com.tomoda.app.
APPLE_SERVICE_ID for Apple SIWA (web/Android) Apple Services ID, e.g. com.tomoda.app.service.
APPLE_TEAM_ID for Apple SIWA Apple Developer team ID.
APPLE_KEY_ID for Apple SIWA Apple Sign-in key ID.
EMAIL_APIKEY yes API key for the transactional email provider (Resend).
SMTP_HOST optional SMTP fallback host (e.g. Mailtrap for local testing).
SMTP_USERNAME optional SMTP username.
SMTP_PASSWORD optional SMTP password.
WEBAUTHN_RP_ID yes (passkeys) from YAML WebAuthn Relying Party ID — domain only, e.g. localhost or tomoda.life.
WEBAUTHN_RP_ORIGIN yes (passkeys) from YAML Full origin including scheme, e.g. http://localhost:3000.
KLIPY_API_KEY for GIF picker Klipy GIF search API key.
PHOTON_URL for self-host geocoder from YAML Base URL of the self-hosted Photon (OSM) geocoder. Falls back to disabled if blank.
FRONTEND_URL yes Used to build email verification + password-reset links.
TWILIO_ACCOUNT_SID for SMS Twilio account SID.
TWILIO_AUTH_TOKEN for SMS Twilio auth token.
TWILIO_PHONE_NUMBER for SMS Twilio sender phone number.
GIN_MODE no release Set to debug for verbose Gin logging.
SERVER_MODE no full Selects what the single backend binary starts. Values: full (everything, default for local), multi-hub (API + WS Hub, prod api deployment), async (Asynq worker + scheduler, prod async deployment), api-hub / ws-hub (reserved for the future api/ws split). Overridable per-pod via --mode flag. See Architecture → Decisions.

Pulling secrets locally

For day-to-day local dev you don't need to copy values into .env.local by hand. Run eval $(./scripts/pull-secrets.sh) to pull from GCP Secret Manager into your shell, then task dev. See scripts.md for the full flow.

Frontend

Expo reads EXPO_PUBLIC_* variables at build time and bakes them into the JS bundle. In development they come from frontend/.env. For release builds, the wrapper script at frontend/scripts/build-with-secrets.sh pulls Sentry values from GCP Secret Manager and execs the build command (see scripts.md).

Variable Required? Default Purpose
EXPO_PUBLIC_API_URL yes Base URL for the REST API, e.g. http://localhost:8080/api/v1 locally, https://api.tomoda.life/api/v1 in production.
EXPO_PUBLIC_WS_URL yes Base URL for the WebSocket endpoint, e.g. wss://api.tomoda.life/ws.
EXPO_PUBLIC_WEB_URL no https://tomoda.life Marketing/web URL used for share links.
EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID yes (Google login) hardcoded fallback Google OAuth web client ID.
EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID yes (iOS) hardcoded fallback Google OAuth iOS client ID.
EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID yes (Android) hardcoded fallback Google OAuth Android client ID.
EXPO_PUBLIC_APP_VERSION no dev App version displayed in the admin dashboard footer. Also sent to Sentry as the release tag.
EXPO_PUBLIC_COMMIT_SHA no local Build commit SHA displayed in the admin dashboard footer.
EXPO_PUBLIC_BUILD_NUMBER no Release build number. Sent to Sentry as the dist tag to disambiguate releases with the same version.
EXPO_PUBLIC_ENV no dev Environment label sent to Sentry (dev / prod). build-with-secrets.sh sets this to prod.
EXPO_PUBLIC_SENTRY_DSN no Sentry project DSN. When empty, Sentry is fully disabled (no init, no captures). Pulled from GCP Secret Manager (tomoda-sentry-dsn) by frontend/scripts/build-with-secrets.sh at release-build time. Required for crash reporting in staging/prod builds — see Frontend → Observability.
EXPO_PUBLIC_SENTRY_ENV no falls back to EXPO_PUBLIC_ENV Optional override if you want a different Sentry environment label from the broader EXPO_PUBLIC_ENV.
SENTRY_AUTH_TOKEN for source-map upload Build-time only, NEVER a public bundle. Auth token used by the Sentry CLI to upload source maps. Pulled from GCP Secret Manager (tomoda-sentry-auth-token) by build-with-secrets.sh. Not prefixed with EXPO_PUBLIC_ precisely because it must not ship to clients.
SENTRY_ORG for source-map upload tomoda Sentry organization slug used by the upload step. Defaults baked into app.config.js — override via env var if your org slug differs.
SENTRY_PROJECT for source-map upload tomoda-frontend Sentry project slug used by the upload step. Defaults baked into app.config.js — override via env var if your project slug differs.

EXPO_PUBLIC_* is public

Anything prefixed with EXPO_PUBLIC_ is baked into the client bundle and shipped to every user. Treat them as public. Never put secrets, server-only API keys, signing keys, database credentials, or anything you wouldn't paste into a tweet behind an EXPO_PUBLIC_ prefix. If you need a value at runtime that must stay secret, fetch it from the backend over an authenticated channel.

Where values come from in prod

Local: .env.local, defaults from pull-secrets.sh. CI / deploy: Cloud Build pulls from GCP Secret Manager via the tomoda-* secret names — see scripts/pull-secrets.sh for the canonical mapping. Native release builds: frontend/scripts/build-with-secrets.sh pulls Sentry values from GCP Secret Manager and execs expo run:*. EXPO_PUBLIC_API_URL / EXPO_PUBLIC_WS_URL are hardcoded in frontend/app.config.js's production block.

If you add a new env var: register it in backend/config/config.go::Load, add it to env.example.local, document it here, and (if it's a secret used in dev/prod) add the GCP Secret Manager mapping to scripts/pull-secrets.sh plus the K8s backend-secrets Secret in the devops/ repo.