Skip to main content

Environment Variables

Comprehensive guide to all environment variables for configuring Aya’s backend and frontend.

Configuration Hierarchy

Aya uses a hierarchical configuration system:
  1. Base config (config.json)
  2. Environment-specific overrides (.env.local, .env.production)
  3. Environment variables (highest priority)
Environment variables use double underscore (__) as the delimiter for nested keys.

Example

config.json
{
  "auth": {
    "jwt_secret": "default-secret"
  },
  "conn": {
    "targets": {
      "default": {
        "protocol": "postgres",
        "dsn": "postgres://localhost:5432/aya"
      }
    }
  }
}
Override with environment variables:
AUTH__JWT_SECRET=production-secret
CONN__targets__default__dsn=postgres://prod-host:5432/aya

Backend Environment Variables

Core Configuration

# Environment (development, production, test)
ENV=production

# Server port
PORT=8080

# Site URI (for CORS, redirects)
SITE_URI=https://aya.is

Authentication

# JWT signing secret (REQUIRED)
AUTH__JWT_SECRET=your-secret-key-here

# JWT expiration (default: 24h)
AUTH__JWT_EXPIRATION=86400

Database

# Database protocol
CONN__targets__default__protocol=postgres

# Full connection string
CONN__targets__default__dsn=postgres://user:password@host:5432/database?sslmode=require

# Connection pool settings
CONN__targets__default__max_open_conns=25
CONN__targets__default__max_idle_conns=10
CONN__targets__default__conn_max_lifetime=3600
Always use sslmode=require in production for secure connections.

External Services

# Resend API key for transactional emails
RESEND__API_KEY=re_123456789

# From email address
RESEND__FROM_EMAIL=[email protected]
RESEND__FROM_NAME=Aya Community

Workers

# Background workers configuration
WORKERS__ENABLED=true

# Telegram bot worker
WORKERS__TELEGRAM_BOT__ENABLED=true
WORKERS__TELEGRAM_BOT__INTERVAL=5s

# Email worker
WORKERS__EMAIL__ENABLED=true
WORKERS__EMAIL__INTERVAL=30s

# Resource sync worker (GitHub, etc.)
WORKERS__RESOURCE_SYNC__ENABLED=true
WORKERS__RESOURCE_SYNC__INTERVAL=1h

Features

# Feature flags
FEATURES__DUMMY=true
FEATURES__TELEGRAM=true
FEATURES__APPLE_AUTH=false

Frontend Environment Variables

Frontend uses VITE_ prefix for variables bundled at build time.
Security: NEVER put secrets in VITE_ variables - they’re exposed in the client bundle!

Build-Time Variables

# Backend API URL (client-side)
VITE_BACKEND_URI=https://api.aya.is

# Frontend host URL
VITE_HOST=https://aya.is

# Telegram bot username (for deep links)
VITE_TELEGRAM_BOT_USERNAME=aya_is_bot

# Environment mode
VITE_MODE=production  # or development

Runtime Variables (SSR)

# Backend URI for server-side requests
BACKEND_URI=http://services:8080

# Node environment
NODE_ENV=production

Feature Flags

# Enable/disable authentication providers
VITE_AUTH_GITHUB_ENABLED=true
VITE_AUTH_APPLE_ENABLED=false

Upload Validation

# Allowed URI prefixes for story uploads (comma-separated)
VITE_ALLOWED_URI_PREFIXES_STORIES=https://objects.aya.is/

# Allowed URI prefixes for profile pictures
VITE_ALLOWED_URI_PREFIXES_PROFILES=https://objects.aya.is/,https://avatars.githubusercontent.com/

Development vs Production

Development (.env.local)

apps/services/.env.local
# Minimal config for local development
AUTH__JWT_SECRET=dev-secret-not-for-production
CONN__targets__default__dsn=postgres://postgres:s3cr3t@localhost:5432/postgres?sslmode=disable

# Optional: Enable Telegram bot in dev
TELEGRAM__ENABLED=true
TELEGRAM__BOT_TOKEN=your-dev-bot-token
TELEGRAM__USE_POLLING=true
WORKERS__TELEGRAM_BOT__ENABLED=true

Production

.env (production)
# Production secrets (NEVER commit to git)
ENV=production
PORT=8080
SITE_URI=https://aya.is

LOG__LEVEL=WARN
LOG__PRETTY=false

# Strong JWT secret (use: openssl rand -hex 32)
AUTH__JWT_SECRET=f3a8b7c2d1e9f6a4b8c7d2e1f9a6b3c8d7e2f1a9b6c3d8e7f2a1b9c6d3e8f7a2

# Production database with SSL
CONN__targets__default__protocol=postgres
CONN__targets__default__dsn=postgres://aya:[email protected]:5432/aya?sslmode=require

# External services
RESEND__API_KEY=re_prod_key
S3__ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
S3__SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
S3__BUCKET=aya-production-uploads
S3__PUBLIC_URL=https://objects.aya.is

# Telegram webhook (not polling)
TELEGRAM__ENABLED=true
TELEGRAM__BOT_TOKEN=prod-bot-token
TELEGRAM__WEBHOOK_URL=https://api.aya.is/telegram/webhook
TELEGRAM__WEBHOOK_SECRET=webhook-secret-key

# Workers
WORKERS__ENABLED=true
WORKERS__TELEGRAM_BOT__ENABLED=false  # Using webhook, not polling
WORKERS__EMAIL__ENABLED=true
WORKERS__RESOURCE_SYNC__ENABLED=true

Docker Compose Integration

compose.yml (Development)

services:
  services:
    environment:
      ENV: development
      PORT: 8080
      LOG__LEVEL: INFO
      LOG__PRETTY: true
      CONN__targets__default__protocol: postgres
      CONN__targets__default__dsn: postgres://postgres:s3cr3t@postgres:5432/postgres?sslmode=disable
    env_file:
      - apps/services/.env.local  # Secrets not in compose.yml

compose.production.yml

services:
  services:
    environment:
      ENV: production
      PORT: 8080
      LOG__LEVEL: WARN
      LOG__PRETTY: false
    env_file:
      - .env  # All secrets from .env file

Secrets Management

Using Docker Secrets

compose.production.yml
services:
  services:
    secrets:
      - jwt_secret
      - db_password
      - s3_secret_key
    environment:
      AUTH__JWT_SECRET_FILE: /run/secrets/jwt_secret
      CONN__targets__default__dsn: postgres://aya:$(cat /run/secrets/db_password)@postgres:5432/aya

secrets:
  jwt_secret:
    external: true
  db_password:
    external: true
  s3_secret_key:
    external: true
Create secrets:
echo "my-jwt-secret" | docker secret create jwt_secret -
echo "db-password" | docker secret create db_password -

Using Kubernetes Secrets

apiVersion: v1
kind: Secret
metadata:
  name: aya-secrets
type: Opaque
stringData:
  jwt-secret: "your-jwt-secret"
  db-password: "your-db-password"
  s3-secret-key: "your-s3-secret"
apiVersion: apps/v1
kind: Deployment
metadata:
  name: aya-backend
spec:
  template:
    spec:
      containers:
      - name: backend
        env:
        - name: AUTH__JWT_SECRET
          valueFrom:
            secretKeyRef:
              name: aya-secrets
              key: jwt-secret
        - name: CONN__targets__default__dsn
          value: "postgres://aya:$(DB_PASSWORD)@postgres:5432/aya"
        envFrom:
        - secretRef:
            name: aya-secrets

Environment Variable Validation

The backend validates required environment variables on startup:
pkg/ajan/configfx/validator.go
type Config struct {
    Env      string `required:"true" validate:"oneof=development production test"`
    Port     int    `required:"true" validate:"min=1,max=65535"`
    SiteURI  string `required:"true" validate:"url"`

    Auth struct {
        JWTSecret string `required:"true" validate:"min=32"`
    }

    Conn struct {
        Targets map[string]struct {
            Protocol string `required:"true" validate:"oneof=postgres mysql"`
            DSN      string `required:"true"`
        }
    }
}
Missing or invalid values will cause startup to fail with clear error messages.

Best Practices

NEVER commit secrets to git:
.gitignore
.env
.env.local
.env.production
Use .env.example as a template:
.env.example
AUTH__JWT_SECRET=change-me
CONN__targets__default__dsn=postgres://user:password@localhost:5432/aya
S3__ACCESS_KEY_ID=your-key-id
S3__SECRET_ACCESS_KEY=your-secret-key
Use cryptographically secure random strings:
# Generate 32-byte hex string
openssl rand -hex 32

# Or base64
openssl rand -base64 32
NEVER use the same JWT secret in development and production.
# Development
AUTH__JWT_SECRET=dev-only-secret

# Production
AUTH__JWT_SECRET=$(openssl rand -hex 32)
Always use sslmode=require in production:
# Development (OK)
CONN__targets__default__dsn=postgres://localhost:5432/aya?sslmode=disable

# Production (REQUIRED)
CONN__targets__default__dsn=postgres://host:5432/aya?sslmode=require

Troubleshooting

Check environment variable names (case-sensitive, use __ not .):
# WRONG
auth.jwt_secret=secret
AUTH.JWT_SECRET=secret

# CORRECT
AUTH__JWT_SECRET=secret
Rebuild frontend after changing VITE_ variables:
cd apps/webclient
deno task build
Or restart dev server:
# Ctrl+C
deno task dev
Ensure secrets are created before starting services:
docker secret ls
# Should show: jwt_secret, db_password, etc.

# Create if missing
echo "secret-value" | docker secret create jwt_secret -

Quick Reference

Essential Variables

Minimal config to get Aya running:
# Backend
AUTH__JWT_SECRET=your-secret-here
CONN__targets__default__dsn=postgres://user:pass@host:5432/db

# Frontend
VITE_BACKEND_URI=http://localhost:8080
VITE_HOST=http://localhost:3000

Full Production Config

See the production example above for a complete configuration.

Next Steps

Docker Deployment

Deploy with environment variables in Docker

Nix Deployment

Use Nix modules for configuration

Development Setup

Configure local environment

Installation

Initial setup guide

Build docs developers (and LLMs) love