Installation
Aya supports two installation methods:
Docker Compose (recommended) - containerized development with all dependencies included
Nix (advanced) - native development with reproducible tooling
Prerequisites
Before installing Aya, ensure you have:
Docker 24.0+ (or OrbStack for macOS)
Git 2.30+
Make (usually pre-installed on Unix/macOS)
4 GB RAM minimum, 8 GB recommended
10 GB free disk space
Nix 2.18+ with flakes enabled
Docker (for PostgreSQL only)
Git 2.30+
4 GB RAM minimum
15 GB free disk space (for Nix store)
Docker Compose Installation
The Docker Compose setup is a complete, isolated environment perfect for development and production deployments.
Configure environment variables
Create the backend environment file: cat > apps/services/.env.local << 'EOF'
# Authentication (REQUIRED)
AUTH__JWT_SECRET=replace-with-secure-random-string
# Optional: Telegram Bot
# TELEGRAM__ENABLED=true
# TELEGRAM__BOT_TOKEN=your-bot-token
# TELEGRAM__BOT_USERNAME=your_bot_username
# TELEGRAM__USE_POLLING=true
# WORKERS__TELEGRAM_BOT__ENABLED=true
# Optional: GitHub OAuth
# AUTH__GITHUB__CLIENT_ID=your-github-oauth-app-id
# AUTH__GITHUB__CLIENT_SECRET=your-github-oauth-secret
# Optional: Apple OAuth
# AUTH__APPLE__ENABLED=true
# AUTH__APPLE__CLIENT_ID=your-apple-client-id
# AUTH__APPLE__TEAM_ID=your-apple-team-id
# AUTH__APPLE__KEY_ID=your-apple-key-id
# AUTH__APPLE__PRIVATE_KEY=your-apple-private-key
# Optional: Email (Resend)
# RESEND__API_KEY=your-resend-api-key
# Optional: S3 Storage
# S3__ENDPOINT=https://s3.amazonaws.com
# S3__ACCESS_KEY_ID=your-access-key
# S3__SECRET_ACCESS_KEY=your-secret
# S3__BUCKET=your-bucket-name
# S3__REGION=us-east-1
EOF
Security : Generate a strong AUTH__JWT_SECRET for production:
Build and start services
This command:
Builds Docker images (first run takes 5-10 minutes)
Starts PostgreSQL with health checks
Runs database migrations automatically
Starts backend API on port 8080
Starts frontend dev server on port 3000
Verify installation
Check that all services are running: Expected output: NAME STATUS PORTS
aya-is-development-postgres-1 Up (healthy) 0.0.0.0:5432->5432/tcp
aya-is-development-services-1 Up 0.0.0.0:8080->8080/tcp
aya-is-development-webclient-1 Up 0.0.0.0:3000->3000/tcp
Test the application: # Backend health check
curl http://localhost:8080/health
# Frontend (should return HTML)
curl http://localhost:3000/en
Docker Compose Configuration
The compose.yml file defines three services:
services :
webclient :
build :
context : .
dockerfile : apps/webclient/Dockerfile
target : development
environment :
VITE_BACKEND_URI : http://localhost:8080
VITE_HOST : http://localhost:3000
BACKEND_URI : http://services:8080 # SSR uses internal network
ports :
- 3000:3000
services :
build :
context : .
dockerfile : apps/services/Dockerfile
target : development-runner
environment :
ENV : development
PORT : 8080
CONN__targets__default__protocol : postgres
CONN__targets__default__dsn : postgres://postgres:s3cr3t@postgres:5432/postgres?sslmode=disable
ports :
- 8080:8080
depends_on :
postgres :
condition : service_healthy
postgres :
image : postgres:16-bookworm
environment :
POSTGRES_PASSWORD : s3cr3t
volumes :
- postgres-data:/var/lib/postgresql/data
ports :
- 5432:5432
Network Architecture : The frontend uses http://services:8080 for SSR (server-to-server) and http://localhost:8080 for client-side fetching.
Nix Installation
The Nix flake provides all development tools in a reproducible environment. Only PostgreSQL runs in Docker.
Install Nix with flakes
If you don’t have Nix installed: # Install Nix (single-user)
sh <( curl -L https://nixos.org/nix/install) --no-daemon
# Enable flakes
mkdir -p ~/.config/nix
echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
Enter Nix development shell
This installs and activates:
Go 1.25
Node.js 20
Deno
pnpm
PostgreSQL 16 client tools
sqlc, air, golangci-lint, govulncheck
go-mockery, gofumpt, gopls, gotools
betteralign, gcov2lcov, pre-commit
Optional : Use direnv to automatically activate the shell when you cd into the project:echo "use flake" > .envrc
direnv allow
Configure environment variables
cat > apps/services/.env.local << 'EOF'
AUTH__JWT_SECRET=your-secret-key-here
EOF
The default config.json already points to localhost:5432 for the database.
Start PostgreSQL
docker compose up -d postgres
Wait for the health check to pass: docker compose ps postgres
# Should show "Up (healthy)"
Run migrations
cd apps/services
make migrate
Start backend and frontend
Open three terminal windows (all within nix develop shell): Terminal 1: Backend
Terminal 2: Frontend
Terminal 3: Commands
cd apps/services
make dev
This starts the Go server with Air hot reload on port 8080. cd apps/webclient
deno task dev
This starts Vite dev server on port 3000. Use this terminal for running tests, linting, etc. # Run backend tests
cd apps/services && make test
# Run frontend tests
cd apps/webclient && deno task test
# Full quality check
make ok
Nix Flake Structure
The flake.nix defines the development environment:
{
description = "aya.is development environment" ;
inputs = {
nixpkgs . url = "github:NixOS/nixpkgs/nixos-unstable" ;
flake-utils . url = "github:numtide/flake-utils" ;
};
outputs = { self , nixpkgs , flake-utils }:
flake-utils . lib . eachDefaultSystem ( system :
let
pkgs = import nixpkgs { inherit system ; };
go = pkgs . go_1_25 ;
nodejs = pkgs . nodejs_20 ;
in {
devShells . default = pkgs . mkShell {
packages = [
go nodejs pkgs . deno pkgs . pnpm
pkgs . git pkgs . gnumake
pkgs . docker pkgs . docker-compose
pkgs . postgresql_16 pkgs . sqlc pkgs . air
pkgs . golangci-lint pkgs . govulncheck
pkgs . go-mockery_2 pkgs . gofumpt
pkgs . gopls pkgs . gotools
pkgs . betteralign pkgs . gcov2lcov
pkgs . pre-commit
];
shellHook = ''pre-commit install --install-hooks > /dev/null 2>&1'' ;
};
}
);
}
Environment Variables Reference
Aya uses a hierarchical configuration system with environment variables taking precedence over config.json.
Backend Configuration
All backend environment variables use double underscore (__) as a separator for nested keys.
Authentication
Database
Telegram Bot
Email & Storage
Logging & Misc
# JWT Secret (REQUIRED)
AUTH__JWT_SECRET = your-secret-here
# GitHub OAuth
AUTH__GITHUB__CLIENT_ID = your-github-app-id
AUTH__GITHUB__CLIENT_SECRET = your-github-secret
# Apple OAuth
AUTH__APPLE__ENABLED = true
AUTH__APPLE__CLIENT_ID = your-apple-client-id
AUTH__APPLE__TEAM_ID = your-team-id
AUTH__APPLE__KEY_ID = your-key-id
AUTH__APPLE__PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
Frontend Configuration
Frontend uses VITE_ prefix for variables bundled at build time:
# Backend API URL (for client-side fetching)
VITE_BACKEND_URI = http://localhost:8080
# Frontend host
VITE_HOST = http://localhost:3000
# Telegram bot username (for deep links)
VITE_TELEGRAM_BOT_USERNAME = aya_is_bot
# Auth provider toggles
VITE_AUTH_GITHUB_ENABLED = true
VITE_AUTH_APPLE_ENABLED = false
# Upload URI validation
VITE_ALLOWED_URI_PREFIXES_STORIES = https://objects.aya.is/
VITE_ALLOWED_URI_PREFIXES_PROFILES = https://objects.aya.is/,https://avatars.githubusercontent.com/
Security : Never put secrets in VITE_ variables - they’re exposed in the client bundle!
Database Setup
Migrations
Database migrations are located in apps/services/etc/data/default/migrations/ and use goose .
# Auto-applied on container start, or manually:
cd apps/services
make migrate
# Check migration status
make migrate-status
# Rollback last migration
make migrate-down
Accessing PostgreSQL
# Via Docker
docker compose exec postgres psql -U postgres -d postgres
# Via local psql (if installed)
psql postgres://postgres:s3cr3t@localhost:5432/postgres
Use any PostgreSQL client:
Host : localhost
Port : 5432
Database : postgres
User : postgres
Password : s3cr3t
Recommended clients:
Schema Overview
Key tables from the initial migration:
CREATE TABLE " profile " (
"id" CHAR ( 26 ) PRIMARY KEY ,
"slug" TEXT UNIQUE NOT NULL ,
"kind" TEXT NOT NULL , -- 'individual', 'organization', 'product'
"profile_picture_uri" TEXT ,
"pronouns" TEXT ,
"created_at" TIMESTAMP WITH TIME ZONE DEFAULT NOW ()
);
CREATE TABLE " profile_tx " (
"profile_id" CHAR ( 26 ),
"locale_code" CHAR ( 12 ), -- e.g., 'en', 'tr', 'pt-PT'
"title" TEXT NOT NULL ,
"description" TEXT NOT NULL ,
PRIMARY KEY ( "profile_id" , "locale_code" )
);
CREATE TABLE " user " (
"id" CHAR ( 26 ) PRIMARY KEY ,
"name" TEXT NOT NULL ,
"email" TEXT ,
"github_handle" TEXT ,
"individual_profile_id" CHAR ( 26 ) REFERENCES "profile"
);
CREATE TABLE " story " (
"id" CHAR ( 26 ) PRIMARY KEY ,
"slug" TEXT UNIQUE NOT NULL ,
"author_profile_id" CHAR ( 26 ) REFERENCES "profile" ,
"kind" TEXT NOT NULL , -- 'article', 'news', 'event'
"cover_picture_uri" TEXT ,
"published_at" TIMESTAMP WITH TIME ZONE
);
CREATE TABLE " story_tx " (
"story_id" CHAR ( 26 ),
"locale_code" CHAR ( 12 ),
"title" TEXT NOT NULL ,
"summary" TEXT ,
"content" TEXT NOT NULL , -- Markdown/MDX
PRIMARY KEY ( "story_id" , "locale_code" )
);
Verification & Testing
After installation, run the full test suite:
This command runs:
Backend: golangci-lint, Go tests, coverage
Frontend: deno lint, deno fmt --check, deno task test:ci
Set up a pre-commit hook to run checks automatically:
Troubleshooting
Docker build fails with 'no space left on device'
Clean up Docker resources: docker system prune -a --volumes
Update flake lock file: nix flake update
nix develop --refresh
PostgreSQL fails to start
Check if port 5432 is already in use: lsof -i :5432
# Kill the process or change the port in compose.yml
Frontend can't connect to backend
Ensure environment variables are set correctly: # For Docker
docker compose exec webclient env | grep VITE
# Should show:
# VITE_BACKEND_URI=http://localhost:8080
# BACKEND_URI=http://services:8080
Next Steps
Architecture Learn about hexagonal architecture and system design
Development Setup Configure your IDE and development workflow
Frontend Guide Start building with Deno, TanStack Start, and React
Backend Guide Explore Go business logic and API development