Phase 0 (CI/CD): fix root typecheck to cover api+worker+web; reconcile migration story into idempotent db:migrate (db:sync + db:triggers); add Gitea Actions quality/deploy/smoke workflow; rewrite README/AGENTS/DEPLOY docs; add requireWorkspaceAccess + recordActivityForEntity conventions. Phase 1 (critical fixes): calendar delete + drag/resize DnD; canvas card CRUD + bulk save + debounced autosave; logout route; graph edge workspaceId derivation; real analytics endpoints (drop Math.random); task board droppable columns + reorder persistence; Tiptap notes editor with sanitized HTML rendering; remove insecure passkey auth; domain/owner scoping (IDOR) on all by-ID routes + search/ export/realtime scoping; command palette routing + agent mention fetch; agent activity SSE handler; graph fly-to with tracked positions. Phase 2 (UX polish): login on design system; Sonner toasts app-wide; shared Loading/Empty/Error state components; working density/sidebarPos/reduce-motion settings; Inter typography; consolidated status-colors lib; unified detail routes; dashboard sort/realtime/responsive fixes; mobile responsive; a11y (radiogroups, sanitized snippets, badge labels). Phase 3 (features): daily notes timezone fix + delete + autosave + mood/energy create; active-domain store + topbar picker; graph domain picker + navigable entity links; tag assign/remove UI + server-side tag filter; real CSV export + import validation; custom fields on tasks. Phase 4 (advanced): migrate job worker into apps/worker (webhook delivery with HMAC, recurring spawn, ai_dispatch disabled); webhook queue helper + entity event enqueuing + test endpoint fix; recurring scheduledJobs pipeline; agents CRUD + permission editing + activity filters; real notifications feed; MCP polish (validation, error codes, domain scoping, dead sql leftover). Phase 5 (E2E + docs): rewrite Playwright suite for the Vite SPA (15 specs, new auth helpers, chromium-only in CI); add ephemeral-Postgres e2e CI job; rewrite docs/API.md for the real Hono API.
6.1 KiB
Project E — Deploy Guide
Production runs as a docker-compose stack on the deploy host (10.0.0.204). The stack has four services: PostgreSQL, the Hono API, the Vite SPA served by Caddy, and the Bun worker. Deploys are normally driven by Gitea Actions, but every step below can be run by hand.
Architecture
Internet → :3000 (SPA container, Caddy)
├── /api/* → api:3000 (Hono/Bun)
├── /mcp* → api:3000 (Hono/Bun)
└── /* → index.html (SPA fallback)
API (:3001, direct) → PostgreSQL (:5432)
Worker → PostgreSQL
Caddy (in the spa container) serves the built SPA and reverse-proxies /api/* and /mcp* to the api service. The API is also exposed directly on :3001 for debugging.
Services
| Service | Container | Image / Build | Ports | Notes |
|---|---|---|---|---|
db |
project-e-db |
postgres:16-alpine |
5432 | Data in the project-e-pg-data volume |
api |
project-e-api |
Dockerfile.api |
3001 → 3000 | Hono on Bun |
spa |
project-e-spa |
Dockerfile.spa |
3000 → 80 | Built Vite SPA + Caddy |
worker |
project-e-worker |
Dockerfile.worker |
none | Bun worker |
All services share the project-e-network bridge and restart unless stopped.
CI/CD pipeline (Gitea Actions)
The workflow lives at .gitea/workflows/ci.yml and runs on the self-hosted runner projecte-runner, which is colocated with the deploy host.
quality— runs on every push and pull request:bun install --frozen-lockfile→bun run typecheck→ web build (cd apps/web && bun run build) →docker compose build. A failed quality gate blocks the deploy job.deploy— runs on pushes tomainand onworkflow_dispatch. It checks out the code and runsbash script/deploy.shwithDEPLOY_DIRdefaulting to/home/projecte/ProjectE.smoke— runs afterdeploy(also on deploy failure): API health athttp://localhost:3000/api/health, SPA root returns HTML, and a login POST to/api/auth/credentialsusingINITIAL_ADMIN_EMAIL/INITIAL_ADMIN_PASSWORDfrom the host.env.
What script/deploy.sh does
Idempotent, safe to re-run. It:
- Syncs the CI checkout into
DEPLOY_DIR(rsync, excluding.git,node_modules, build artifacts, and.env) - Loads secrets from the host
.env(never overwrites it) - Installs dependencies with
bun install --frozen-lockfile - Runs
bun run db:migrate - Runs
docker compose buildanddocker compose up -d - Waits up to 60s for
http://localhost:3000/api/healthto return HTTP 200, dumping recent API logs if it times out
Secrets
Secrets live in the host .env at /home/projecte/ProjectE/.env. This file is gitignored; never commit it. To set it up:
cp .env.example .env
Fill in POSTGRES_PASSWORD, DATABASE_URL, AUTH_SECRET (or NEXTAUTH_SECRET), INITIAL_ADMIN_EMAIL, INITIAL_ADMIN_PASSWORD, and, as needed, NODE_ENV, PUBLIC_URL, COOKIE_SECURE, and ALLOWED_HOSTS. deploy.sh sources it, and docker-compose reads the POSTGRES_PASSWORD and DATABASE_URL values from it.
Manual deploy
On the deploy host:
cd /home/projecte/ProjectE
# Pull latest
git pull origin main
# Install dependencies
bun install
# Apply schema + triggers (idempotent)
bun run db:migrate
# Build images
docker compose build
# Restart the stack
docker compose up -d
# Wait for API health
until curl -s http://localhost:3000/api/health | grep -q '"status"'; do sleep 2; done
# Check status
docker compose ps
Database migrations
bun run db:migrate chains two idempotent steps:
db:sync—drizzle-kit push --force, which syncs the schema inpackages/db/src/schema.tsto the databasedb:triggers—script/apply-triggers.ts, which applies the search-vector triggers fromdrizzle/0005_search_vector_trigger.sql(CREATE OR REPLACE FUNCTION+DROP TRIGGER IF EXISTS)
Both are safe to run on every deploy. Schema changes go through bun run db:generate in development, then land in drizzle/ before the next deploy.
Rollback
Compose images are rebuilt from the checkout, so there are no pinned image tags to restore. To roll back a bad release:
- Revert the checkout to the previous good commit:
git revert <sha>(orgit checkout <sha>) and push tomain - Re-run the manual deploy steps (
docker compose build,docker compose up -d)
The project-e-pg-data volume is untouched by deploys and rollbacks, so the database survives both. If a deploy failed, deploy.sh exits non-zero with the recent API logs; do not force it past a failing health check.
Logs
# All services
docker compose logs --tail=50 -f
# Specific service
docker compose logs --tail=50 -f api
docker compose logs --tail=50 -f spa
docker compose logs --tail=50 -f worker
docker compose logs --tail=50 -f db
Debugging
API health (direct, :3001)
curl http://localhost:3001/api/health
Returns {"status":"ok", ...} with a database ping (database.connected, database.ping_ms).
API health (through Caddy)
curl http://localhost:3000/api/health
SPA health check
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/
Login test
curl -s -X POST http://localhost:3000/api/auth/credentials \
-H "Content-Type: application/json" \
-d '{"email":"<INITIAL_ADMIN_EMAIL>","password":"<INITIAL_ADMIN_PASSWORD>"}'
Expect HTTP 200 and a session cookie.
Container health
docker inspect project-e-db --format '{{.State.Health.Status}}'
Restart or rebuild one service
docker compose restart api
docker compose build spa
docker compose up -d --force-recreate spa
Important notes
- The
project-e-pg-dataDocker volume contains the live database. Do not delete it. Back it up (volume snapshot orpg_dump) before major schema work. - Port 3000 is the SPA (Caddy); port 3001 is the API directly (for debugging).
- The MCP endpoint requires a valid API key (separate from JWT auth).
- The root
worker/directory is legacy. The active worker isapps/worker.