Vardo

Contributing

Development setup, project structure, code quality guidelines, and PR workflow for contributing to Vardo.

Prerequisites

  • Node.js 22+
  • pnpm (npm install -g pnpm)
  • Docker Desktop (or Docker Engine + Compose plugin on Linux)
  • Git

First-time setup

git clone https://github.com/joeyyax/host vardo
cd vardo
pnpm install
cp .env.example .env

Generate the encryption key:

openssl rand -hex 32

Paste the output as ENCRYPTION_MASTER_KEY in .env. The other values in .env.example work as-is for local development.


Starting the dev stack

# Start Postgres + Redis (and cAdvisor, Loki)
docker compose up -d

# Push the schema to the local database
pnpm db:push

# Start the Next.js dev server
pnpm dev

App is at http://localhost:3000.

COMPOSE_PROFILES is not set in dev -- the frontend container is skipped. You run the app directly with pnpm dev for hot reload.

Dev service ports

ServicePort
PostgreSQL7100
Redis7200
cAdvisor7300
Loki7400
Traefik dashboard8080 (if enabled)
Next.js app3000

Database workflow

# Apply schema changes immediately (dev only -- no migration file)
pnpm db:push

# Browse the database with a GUI
pnpm db:studio

# Generate a migration file
pnpm db:generate

# Run migrations (used in production start script)
pnpm db:migrate

Schema lives in lib/db/schema.ts. Queries are organized by domain in lib/db/queries/.


Running checks

Run these before pushing:

pnpm typecheck   # TypeScript -- must pass
pnpm lint        # ESLint -- must pass
pnpm test        # Runs typecheck + lint + vitest
pnpm test:unit   # Vitest only
pnpm test:e2e    # Playwright end-to-end tests

CI runs the same checks. PRs with failing typecheck or lint won't merge.

CI pipeline

Planned -- Tracked in #165

Automated CI runs on every PR aren't configured yet. The intent is to run pnpm typecheck, pnpm lint and pnpm test in GitHub Actions on every push to a PR branch. Until this is set up, run checks locally before pushing.

Integration tests for critical paths

Planned -- Tracked in #296

The current test suite covers unit-level logic. Integration tests for critical paths -- deploy pipeline, backup job execution, webhook handling, auth flows -- are planned but not yet written.

If you're adding a new critical path (deploy trigger, backup target, auth method), consider opening a follow-up issue to track the needed integration test.


Project structure

The repo is a flat Next.js app -- no monorepo, no apps/ prefix.


Architecture overview

Multi-tenant by design. Every database query is scoped by organization_id. There are no global queries that return data across orgs.

API-first. The frontend calls the versioned REST API at /api/v1/organizations/[orgId]/.... Server Actions are used for simple form submissions; anything complex goes through the API.

Config resolution chain. Application config (email, GitHub App, feature flags) resolves in this order:

  1. vardo.yml / vardo.secrets.yml (file-based -- takes priority)
  2. Database (admin UI)
  3. Defaults

Auth. Better Auth handles all auth -- passkeys, magic links, OAuth, 2FA. Session validation uses lib/auth/session.ts. Admin checks use lib/auth/admin.ts.

Deploy types. Six deploy strategies: compose, dockerfile, image, static, nixpacks, railpack. Defined in the deploy_type enum in lib/db/schema.ts.


Branch conventions

PrefixPurpose
feat/New features
fix/Bug fixes
chore/Cleanup, refactoring, deps
docs/Documentation

Branch from main. Keep branches focused -- one feature or fix per branch.


PR workflow

  1. Branch from main
  2. Work incrementally, commit logical units
  3. Run pnpm typecheck before pushing
  4. Push and create a PR with the relevant review labels
  5. Gating reviews must pass before merge
  6. Generative reviews create follow-up issues
  7. review:final is the last gate -- regression check, scope fit, clean commit history
  8. Squash merge to main

Commit messages

Imperative mood, one logical change per commit:

Add backup target PATCH endpoint
Fix org switcher flicker on mobile

Squash granular commits before merge.


Review labels

Gating (must pass before merge)

LabelScope
review:securityInjection, auth, rate limiting, headers
review:architecturePatterns, duplication, ports and adapters
review:frontendUX code quality, performance, visual
review:infraDocker, compose, deploy, install scripts
review:performanceN+1 queries, re-renders, bundle size, hot paths
review:databaseSchema design, migration safety, indexes, query patterns
review:accessibilityWCAG, keyboard nav, screen reader, contrast
review:fullAll gating reviews
review:finalLast gate -- regression check, scope, commit history

Generative (create follow-up work)

LabelScope
review:docsDraft user-facing docs for new features
review:apiAPI surface consistency and discoverability
review:testingIdentify needed tests
review:uxUser flows, empty/error/loading states, microcopy
review:devexCode ergonomics, types, patterns

Code quality guidelines

  • TypeScript strict mode -- pnpm typecheck must pass
  • No any types unless genuinely unavoidable (add a comment explaining why)
  • Prefer ports and adapters for infrastructure boundaries (e.g., lib/email/ abstracts the provider)
  • All queries must be org-scoped
  • New API routes need auth checks -- see existing routes for the pattern

How to add a new API endpoint

  1. Create the route file under app/api/v1/organizations/[orgId]/your-resource/route.ts
  2. Use the verifyOrgAccess helper from lib/api/verify-access.ts to validate the session and org membership -- it returns { organization, membership, session } or null if forbidden
  3. Extract orgId from params
  4. Write queries in lib/db/queries/your-resource.ts
  5. Return JSON with standard error shapes ({ "error": "message" })
  6. Wrap the handler with withRateLimit from lib/api/with-rate-limit.ts and choose the appropriate tier
  7. Add review:security and review:database labels to the PR

How to add a new settings page

Settings pages live under app/(authenticated)/settings/[tab]/ (org settings) or app/(authenticated)/user/settings/[tab]/ (user settings). Admin settings are under app/(authenticated)/admin/settings/[tab]/.

  1. Add the tab to the settings nav in components/settings-nav.tsx
  2. Create the page component in the appropriate settings directory
  3. Use Server Components to load initial data, Client Components for interactive forms
  4. Mutations go through Server Actions or API routes
  5. Use toast from sonner for success/error feedback

How to add a new deploy template

Deploy templates live in templates/. Each template is a TOML file that defines the image, environment variables, volumes, ports and connection info.

  1. Create a TOML template file in templates/
  2. The template is automatically registered on seed
  3. The template will appear in the new app wizard automatically
  4. Add a seed entry if it should be available on fresh installs (update seed_templates in install.sh)

On this page