# ResolutionFlow — Dev Environment Setup & Operations Guide > **Scope:** Stand up a working ResolutionFlow dev environment from scratch on any Linux host (VPS, on-prem Proxmox LXC/VM, bare metal). Self-contained — do not read another doc to get the dev stack running. > **Last rewritten:** April 2026, post-Hostinger-VPS deprecation, ahead of Proxmox migration. > **Audience:** You (returning to the project), a teammate, or a fresh Claude Code session. If you're picking up mid-migration and need to know what code state is on the current branch, read `docs/FlowAssist_Migration/MIGRATION-HANDOFF.md` first. --- ## 1. What this project needs, regardless of host These are non-negotiable. If your host can't provide them, fix that before anything else. | Component | Required version | Notes | |---|---|---| | **Linux** | any mainstream distro | Ubuntu 22.04+ / Debian 12+ tested; Alpine fine for containers | | **Python** | 3.11+ | Backend and migrations | | **Node.js** | 20.19+ | Vite 7 fails on older versions — CLAUDE.md Lesson 63 | | **PostgreSQL** | 16 | `gen_random_uuid()` + `jsonb` + RLS are all leaned on | | **Docker + Docker Compose** | recent | Only if you are running Postgres and/or backend as containers | | **Git** | recent | | Optional but recommended: | Tool | Why | |---|---| | **code-server** | Browser-based VS Code; how this project has historically been edited | | **`gh` CLI** | Mirror repo is on GitHub via Gitea; `gh` reads issues and PRs | | **bun** | Required for the gstack `/browse` + `/qa` skills (CLAUDE.md Lesson 82) | | **`npx gitnexus analyze`** | Code-graph for Phase 2+ work that touches `unified_chat_service` | | **Claude Code CLI** | If you want to run Claude Code locally on the host | --- ## 2. Architectural shape The project is three services plus your editor. Keep these facts in mind regardless of topology: ``` Your browser ├─► code-server (editor, optional — usually port 8080 or behind TLS) ├─► frontend (Vite) (dev server, port 5173) └─► backend (FastAPI) (dev server, port 8000) │ └─► PostgreSQL (port 5432) ``` **The frontend calls the backend by URL at runtime.** The frontend does not proxy through the backend. Whatever URL your browser uses to reach the backend is what `VITE_API_URL` must be set to, **baked in at build time**. Changing `VITE_API_URL` requires rebuilding the frontend. **The backend calls the database by URL at runtime.** The URL depends on where Postgres is relative to the backend — Docker service name if both are in the same compose network, `localhost` if Postgres is native on the same host, or a DNS name if they're in separate containers/VMs. **CORS is configured explicitly.** The backend's `CORS_ORIGINS` list must include every origin your browser will use to reach the frontend. A missing origin shows up as failed preflight requests. --- ## 3. Topology choices — pick one before you start The project is agnostic to topology, but each shape has different setup steps. ### Option A — all-in-one LXC/VM/host (simplest) Postgres, backend, and frontend all run on one Linux host. code-server runs on the same host or a sibling. No Docker required. Best for a single-developer Proxmox LXC. ### Option B — Docker Compose on one host Postgres, backend, and frontend run as Docker containers on one host. code-server runs outside the compose network (on the host or in another container). This is how the old Hostinger VPS was configured. Best if you want reproducible container images. ### Option C — split services across containers/VMs Postgres in one container/VM, backend and frontend in another, code-server in a third. Most complex; requires explicit networking between them. Use only if you have a specific reason. **Pick one and stick with it for the entire setup.** Mixing Options A and B halfway through is where setup runs off the rails. --- ## 4. Per-host configuration These values are specific to your host. Fill them in once and reference them by name throughout the rest of the doc. ``` DEV_HOST = DEV_HOST_SCHEME = FRONTEND_PORT = 5173 BACKEND_PORT = 8000 POSTGRES_PORT = 5432 # or 5433 if you're avoiding conflict with a host Postgres POSTGRES_DB_NAME = resolutionflow POSTGRES_USER = postgres POSTGRES_PASSWORD = SECRET_KEY = ANTHROPIC_API_KEY = GOOGLE_AI_API_KEY = ``` Store these somewhere you can copy from during setup. Do not commit them. > **Naming note:** the canonical database name is `resolutionflow`. If you see `patherly` in a config file, that's drift from an earlier rename and is being swept in a separate commit — use `resolutionflow`. CLAUDE.md tracks the live-code files that still reference `patherly`. --- ## 5. Setup procedure Run these in order. Stop at the first failure and investigate. ### 5.1 Install system dependencies ```bash # Ubuntu / Debian sudo apt update && sudo apt install -y \ git curl build-essential \ python3.11 python3.11-venv python3-pip \ postgresql-client # not the server — only if running Postgres natively # Node 20 via nvm (survives container rebuilds if stored in a volume) curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash export NVM_DIR="$HOME/.nvm" && source "$NVM_DIR/nvm.sh" nvm install 20 nvm alias default 20 ``` For Option B (Docker Compose), also: ```bash curl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER # log out and back in for this to take effect ``` ### 5.2 Clone the repo ```bash git clone https://gitea.resolutionflow.com/chihlasm/resolutionflow.git # or the GitHub mirror: # git clone https://github.com/chihlasm/resolutionflow.git cd resolutionflow # Check out the working branch if you're continuing mid-migration. git fetch origin git checkout feat/flowpilot-migration ``` ### 5.3 Start PostgreSQL **Option A (native Postgres on the host):** ```bash sudo apt install -y postgresql-16 sudo -u postgres psql -c "CREATE DATABASE resolutionflow;" sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'postgres';" # Adjust pg_hba.conf if you need non-local connections. ``` **Option B (Postgres via Docker Compose):** The repo has a `docker-compose.dev.yml` at the root. Check its Postgres service for the container name, port mapping, and volume. CLAUDE.md Lesson 65 notes the local compose defaults use container name `resolutionflow_postgres`, database `resolutionflow`, port `5433` mapped to the host. Confirm what the compose file actually says on your branch before trusting those values. ```bash docker compose -f docker-compose.dev.yml up -d db docker compose -f docker-compose.dev.yml logs db # wait for "ready to accept connections" ``` **Verify:** ```bash # From the host (Option A) or the backend container/LXC (Option B): psql -h -p -U postgres -d resolutionflow -c "SELECT now();" ``` ### 5.4 Write the `.env` files The repo expects three env files. Create each one: **`backend/.env`** — backend source of truth: ```bash APP_NAME=ResolutionFlow DEBUG=true # DB URLs — `` is `localhost` for Option A, the Docker service name # (e.g. `db`) for Option B, or the DB container/VM hostname for Option C. DATABASE_URL=postgresql+asyncpg://postgres:postgres@:/resolutionflow DATABASE_URL_SYNC=postgresql://postgres:postgres@:/resolutionflow # Auth SECRET_KEY= ACCESS_TOKEN_EXPIRE_MINUTES=5 REFRESH_TOKEN_EXPIRE_DAYS=7 REQUIRE_INVITE_CODE=true # AI providers AI_PROVIDER=anthropic ANTHROPIC_API_KEY= GOOGLE_AI_API_KEY= # FlowPilot MCP telemetry — leave on so the Phase 0.5 baseline data keeps accruing ENABLE_MCP_MICROSOFT_LEARN=true # CORS + frontend URL FRONTEND_URL=://: CORS_ORIGINS=["http://localhost:5173","http://127.0.0.1:5173","://:"] ``` **`frontend/.env.local`** — frontend build-time config: ```bash VITE_API_URL=://: ``` Optional PostHog (CLAUDE.md Lesson 64 — enables product analytics locally): ```bash VITE_PUBLIC_POSTHOG_KEY= VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com ``` **Repo root `.env`** — only needed for Option B (Docker Compose interpolation): ```bash SECRET_KEY= ANTHROPIC_API_KEY= GOOGLE_AI_API_KEY= POSTGRES_PORT= ``` > **Never commit any `.env` file.** The `.gitignore` already covers this. ### 5.5 Run the backend setup **Option A (native):** ```bash cd backend python3.11 -m venv venv source venv/bin/activate pip install -r requirements.txt # Migrate the DB to head. alembic upgrade head ``` **Option B (Docker):** ```bash docker compose -f docker-compose.dev.yml up -d backend docker compose -f docker-compose.dev.yml run --rm backend alembic upgrade head ``` **Expected alembic head** (as of `feat/flowpilot-migration`): `f07010f17b01`. If `alembic current` shows anything else after `upgrade head`, something has gone wrong — stop and investigate. ### 5.6 Seed test users ```bash # Option A cd backend && source venv/bin/activate python -m scripts.seed_test_users # Option B docker exec resolutionflow_backend python -m scripts.seed_test_users ``` Test users (all share password `TestPass123!`): | Email | Role | |---|---| | `admin@resolutionflow.example.com` | super admin | | `teamadmin@resolutionflow.example.com` | team admin | | `engineer@resolutionflow.example.com` | engineer | | `pro@resolutionflow.example.com` | solo pro | ### 5.7 Run the backend **Option A:** ```bash cd backend && source venv/bin/activate uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload ``` **Option B:** Already running from `docker compose up -d backend`. Tail logs: ```bash docker compose -f docker-compose.dev.yml logs -f backend ``` **Verify:** `curl ://:/api/docs` — OpenAPI docs page loads. ### 5.8 Run the frontend **Option A:** ```bash cd frontend npm install npm run dev -- --host 0.0.0.0 --port 5173 ``` **Option B:** ```bash docker compose -f docker-compose.dev.yml up -d --build frontend ``` **Verify:** Open `://:` in your browser. Log in with one of the test users. Navigate to `/pilot` — the FlowPilot session page should render. --- ## 6. Verification — proof the env actually works Run these after setup. Every item has a concrete expected outcome. ### 6.1 Database schema is at the right version ```bash # Option A cd backend && source venv/bin/activate && alembic current # Option B docker compose -f docker-compose.dev.yml run --rm backend alembic current ``` Expected: `f07010f17b01 (head)` on the `feat/flowpilot-migration` branch. On `main`, expected: `074 (head)`. ### 6.2 Alembic reversibility ```bash alembic downgrade -1 # should complete cleanly alembic upgrade head # should return to f07010f17b01 ``` If either step fails, the migration has a bug and Phase 2 cannot start. ### 6.3 Prompt-cache hit verification (the deferred Phase 0 TODO) `backend/app/core/ai_provider.py` module docstring has a `TODO(phase0-verify)` note describing this. Procedure: 1. Confirm `AI_PROVIDER=anthropic` and `ANTHROPIC_API_KEY` is set in `backend/.env`. 2. Start the backend with log level INFO or lower. 3. In the UI, open `/pilot` and send a chat message. Wait a few seconds for the response. 4. Send a second chat message in the same session, within 5 minutes of the first. 5. In backend logs, grep for lines containing `anthropic.cache`: ```bash # Option A grep 'anthropic.cache' # Option B docker compose -f docker-compose.dev.yml logs backend | grep 'anthropic.cache' ``` 6. Expected: two `anthropic.cache` log events. First has `cache_creation_input_tokens > 0`. Second has `cache_read_input_tokens > 0`. 7. If the second shows zero reads, inspect the prompt prefix for silent invalidators (timestamps, unsorted JSON keys, varying tool list ordering). Fix before proceeding with any Phase 2 work. ### 6.4 Frontend build is TypeScript-clean ```bash cd frontend npx tsc -b # no errors npm run build # no errors ``` CLAUDE.md Lesson 105 notes that `npm run build` may fail with an `EACCES` on `dist/` inside code-server — that is a Docker filesystem permission issue, not a real build error. Use `npx tsc -b` to verify TypeScript cleanliness in that case. ### 6.5 `/assistant` → `/pilot` redirect Open `://:/assistant/` in the browser. Expected: URL changes to `/pilot/`; the FlowPilot session page renders. Bare `/assistant` redirects to bare `/pilot`. ### 6.6 Dispatcher de-branching Navigate to the dashboard. Click a session in `ActiveFlowPilotSessions` or `RecentFlowPilotSessions`. Expected: routes to `/pilot/:id` regardless of the session's `session_type` value. (Check the browser URL bar.) ### 6.7 CORS Open the browser DevTools Network tab, navigate to any backend-hitting page. Expected: no CORS errors. If you see "blocked by CORS policy," the missing origin needs adding to `backend/.env`'s `CORS_ORIGINS`. --- ## 7. Runbook Day-to-day commands after setup is complete. ### Restart services ```bash # Option A # backend — Ctrl-C and re-run uvicorn # frontend — Ctrl-C and re-run npm run dev # Option B docker compose -f docker-compose.dev.yml restart backend docker compose -f docker-compose.dev.yml up -d --build frontend # rebuild required if VITE_* changed docker compose -f docker-compose.dev.yml down && docker compose -f docker-compose.dev.yml up -d # full restart ``` ### Apply a new migration ```bash # Option A cd backend && source venv/bin/activate && alembic upgrade head # Option B docker compose -f docker-compose.dev.yml run --rm backend alembic upgrade head ``` ### Create a new migration ```bash # Option A cd backend && source venv/bin/activate alembic revision -m "short description" # manual, preferred per CLAUDE.md Lesson 77 # OR alembic revision --autogenerate -m "description" # pulls in drift; review carefully ``` Never pass `--rev-id` — let Alembic generate the hex hash. ### Inspect the database ```bash # Option A (native Postgres) psql -h localhost -p 5432 -U postgres -d resolutionflow # Option B (Docker) docker exec -it resolutionflow_postgres psql -U postgres -d resolutionflow ``` ### Run tests ```bash # Option A cd backend && source venv/bin/activate pytest --override-ini="addopts=" # Option B docker compose -f docker-compose.dev.yml run --rm backend pytest --override-ini="addopts=" ``` First time only, create the test database: ```bash # Option A sudo -u postgres psql -c "CREATE DATABASE resolutionflow_test;" # Option B docker exec -it resolutionflow_postgres psql -U postgres -c "CREATE DATABASE resolutionflow_test;" ``` ### View backend logs ```bash # Option A: wherever you ran uvicorn # Option B docker compose -f docker-compose.dev.yml logs -f --tail=100 backend ``` Structured events to grep for: - `anthropic.cache` — prompt-cache hit/creation telemetry (Phase 0.1) - `mcp.turn` — per-turn MCP availability/invocation (Phase 0.5) - `mcp.fallback` — MCP silent-retry fallback fired (Phase 0.5) --- ## 8. Troubleshooting ### CORS errors in the browser The backend did not accept the origin your browser used. Check `backend/.env`'s `CORS_ORIGINS` — it must include the exact scheme + host + port the browser sent. Restart the backend after editing. ### `VITE_API_URL` points at the wrong place The frontend was built with a stale value. Rebuild the frontend. Option B: `docker compose up -d --build frontend`. Option A: restart `npm run dev`. ### `alembic upgrade head` fails with "target database is not up to date" Your DB migration chain is out of sync with the code. On a dev box, the safe recovery is to drop the DB and re-migrate from scratch: ```bash # Option A sudo -u postgres psql -c "DROP DATABASE resolutionflow;" -c "CREATE DATABASE resolutionflow;" cd backend && source venv/bin/activate && alembic upgrade head # Option B docker exec resolutionflow_postgres psql -U postgres -c "DROP DATABASE resolutionflow;" -c "CREATE DATABASE resolutionflow;" docker compose -f docker-compose.dev.yml run --rm backend alembic upgrade head ``` Only do this on a dev box — it destroys all local data. ### `alembic heads` shows more than one head Only on a local branch that has diverged from `origin/main`. Production `main` has a single head. If this happens on a fresh clone, one of your local migration files has the wrong `down_revision`. Inspect each file's `down_revision` and reconnect the chain. ### Frontend build fails with "EACCES: permission denied" on `dist/` Filesystem permission issue inside the code-server container (CLAUDE.md Lesson 105). TypeScript compilation itself completes — use `npx tsc -b` to verify cleanliness without needing to write to `dist/`. ### `docker` command not found inside code-server If your code-server is itself inside a container, Docker is probably not exposed to it. CLAUDE.md Lesson 103 was written for this case on the old VPS. On Proxmox, the fix depends on topology — either SSH to the host to run Docker commands, or mount the host's Docker socket into the code-server container. ### Backend returns 500 with `InsufficientPrivilegeError: new row violates row-level security policy` RLS is enabled on a table your code wrote to without the right `account_id`. CLAUDE.md Lessons 107, 108, 110 cover this family of bugs. The fix is always at the service layer: make sure every model creation passes `account_id=` explicitly, and that startup routines that touch tenant-isolated tables use `_admin_session_factory()` rather than `get_db()`. ### Anthropic cache reads are zero on the second turn Something in the cached prefix is changing between turns. Inspect the system-block list and the first N history messages for timestamps, `datetime.now()`, unsorted dict keys in JSON prompts, or varying tool-list order. The `anthropic.cache` telemetry shows exactly how many tokens were read vs created — use it to narrow down the invalidator. --- ## 9. Security posture for dev environments This doc is about dev, not production. But: - Never commit `.env` files. The `.gitignore` covers this. - `SECRET_KEY` should be generated per-host, not reused across environments. - `ANTHROPIC_API_KEY` is billable — rotate if leaked into logs or chat. - Postgres on a dev host should not be exposed to the internet. Bind it to `127.0.0.1` or to a private network interface only. - If you expose the frontend or backend publicly (for teammates to test against), put it behind TLS with a real certificate. Do not let dev credentials travel over plain HTTP on the public internet. --- ## 10. What's not in this doc - **Production deployment.** This is a dev-env doc. Production lives on Railway — see `CLAUDE.md`'s Deployment section. - **How to set up Traefik or any particular reverse proxy.** Whichever proxy you use is your choice; the dev stack just needs something that routes `:5173` and `:8000` to the right services. - **How to configure code-server itself.** Install it however you prefer (native, Docker, LXC); point it at the repo, and the rest of this doc applies. - **Where to host the Proxmox instance.** Up to you. If something in this doc turns out to be wrong on your host, fix the doc. This is a living document — the whole point of rewriting it from the Hostinger-specific version was to make it survive host changes.