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 .envGenerate the encryption key:
openssl rand -hex 32Paste 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 devApp 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
| Service | Port |
|---|---|
| PostgreSQL | 7100 |
| Redis | 7200 |
| cAdvisor | 7300 |
| Loki | 7400 |
| Traefik dashboard | 8080 (if enabled) |
| Next.js app | 3000 |
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:migrateSchema 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 testsCI 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:
vardo.yml/vardo.secrets.yml(file-based -- takes priority)- Database (admin UI)
- 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
| Prefix | Purpose |
|---|---|
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
- Branch from
main - Work incrementally, commit logical units
- Run
pnpm typecheckbefore pushing - Push and create a PR with the relevant review labels
- Gating reviews must pass before merge
- Generative reviews create follow-up issues
review:finalis the last gate -- regression check, scope fit, clean commit history- Squash merge to main
Commit messages
Imperative mood, one logical change per commit:
Add backup target PATCH endpoint
Fix org switcher flicker on mobileSquash granular commits before merge.
Review labels
Gating (must pass before merge)
| Label | Scope |
|---|---|
review:security | Injection, auth, rate limiting, headers |
review:architecture | Patterns, duplication, ports and adapters |
review:frontend | UX code quality, performance, visual |
review:infra | Docker, compose, deploy, install scripts |
review:performance | N+1 queries, re-renders, bundle size, hot paths |
review:database | Schema design, migration safety, indexes, query patterns |
review:accessibility | WCAG, keyboard nav, screen reader, contrast |
review:full | All gating reviews |
review:final | Last gate -- regression check, scope, commit history |
Generative (create follow-up work)
| Label | Scope |
|---|---|
review:docs | Draft user-facing docs for new features |
review:api | API surface consistency and discoverability |
review:testing | Identify needed tests |
review:ux | User flows, empty/error/loading states, microcopy |
review:devex | Code ergonomics, types, patterns |
Code quality guidelines
- TypeScript strict mode --
pnpm typecheckmust pass - No
anytypes 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
- Create the route file under
app/api/v1/organizations/[orgId]/your-resource/route.ts - Use the
verifyOrgAccesshelper fromlib/api/verify-access.tsto validate the session and org membership -- it returns{ organization, membership, session }ornullif forbidden - Extract
orgIdfrom params - Write queries in
lib/db/queries/your-resource.ts - Return JSON with standard error shapes (
{ "error": "message" }) - Wrap the handler with
withRateLimitfromlib/api/with-rate-limit.tsand choose the appropriate tier - Add
review:securityandreview:databaselabels 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]/.
- Add the tab to the settings nav in
components/settings-nav.tsx - Create the page component in the appropriate settings directory
- Use Server Components to load initial data, Client Components for interactive forms
- Mutations go through Server Actions or API routes
- Use
toastfromsonnerfor 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.
- Create a TOML template file in
templates/ - The template is automatically registered on seed
- The template will appear in the new app wizard automatically
- Add a seed entry if it should be available on fresh installs (update
seed_templatesininstall.sh)