Skip to content

Manual setup

Running the backend, database, Redis, Celery and frontend directly on your machine, without Docker. Useful if you want faster iteration on backend code than a bind-mounted container gives you, or if you're setting up on a host where Docker isn't available.

Prerequisites

  • Python 3.12 or later: backend/pyproject.toml declares requires-python = ">=3.12", and backend/.python-version pins 3.12.
  • uv: the backend is managed with uv, not pip or a manually created virtualenv. uv sync reads backend/pyproject.toml and backend/uv.lock and creates a .venv/ for you; every backend command in this guide runs via uv run <cmd>, which resolves that venv automatically. Install it once, system-wide:

curl -LsSf https://astral.sh/uv/install.sh | sh
# or, on macOS: brew install uv
- PostgreSQL: the only supported database backend (django.db.backends.postgresql in backend/crm/settings.py); Row-Level Security depends on it. - Redis: used as the Celery broker and result backend. - Node.js and pnpm: for the SvelteKit frontend in frontend/.

Backend

From backend/:

cd backend
uv sync

This creates .venv/ and installs everything listed in pyproject.toml's dependencies (Django, DRF, Celery, djangorestframework-simplejwt, weasyprint, etc.) at the versions locked in uv.lock.

Copy the example environment file and adjust it for your local database and Redis:

cp .env.example .env

backend/crm/settings.py loads this file via python-dotenv (load_dotenv() at import time), so anything you put in .env is picked up the next time you run a management command. At minimum, review DBNAME, DBUSER, DBPASSWORD, DBHOST, DBPORT against the database role you create in the next section.

Once .env points at a reachable database, apply migrations:

uv run python manage.py migrate

And run the dev server:

uv run python manage.py runserver

By default this serves on http://127.0.0.1:8000.

Database

The Django DATABASES setting (backend/crm/settings.py) reads DBNAME, DBUSER, DBPASSWORD, DBHOST and DBPORT from the environment, defaulting to a local postgres superuser connection if they're unset. Don't leave it on that default: the role Django connects as must not be a PostgreSQL superuser, or Row-Level Security is bypassed for every query that role makes, regardless of what the RLS policies say.

Create a dedicated, non-superuser role and database, for example:

sudo -u postgres psql
CREATE DATABASE crm_db;
CREATE USER crm_user WITH PASSWORD 'crm_password';
GRANT ALL PRIVILEGES ON DATABASE crm_db TO crm_user;
\connect crm_db;
GRANT ALL ON SCHEMA public TO crm_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO crm_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO crm_user;

Then point .env at it (DBNAME=crm_db, DBUSER=crm_user, DBPASSWORD=crm_password, DBHOST=localhost, DBPORT=5432) and run uv run python manage.py migrate.

You can confirm the role is configured correctly with the RLS management command:

uv run python manage.py manage_rls --status
uv run python manage.py manage_rls --verify-user

Redis and Celery

Redis needs to be reachable at the URL in CELERY_BROKER_URL / CELERY_RESULT_BACKEND (both default to redis://localhost:6379/0 in .env.example). Install it locally through your package manager, or run the same image the Docker setup uses:

docker run -p 6379:6379 redis:7-alpine

With Redis reachable, start a Celery worker from backend/:

uv run celery -A crm worker --loglevel=INFO

backend/crm/celery.py also defines a set of periodic tasks (recurring-invoice generation, overdue-invoice and expired-estimate checks, stale-opportunity and goal-milestone checks, SLA breach scanning, notification and refresh-token cleanup) on cron-style schedules. To run those on schedule you also need Celery beat, in a separate terminal:

uv run celery -A crm beat --loglevel=INFO

Neither process is required for the API itself to respond to requests. They only matter for background and scheduled work.

Frontend

From frontend/:

cd frontend
pnpm install

Copy the example environment file:

cp .env.example .env

At minimum, PUBLIC_DJANGO_API_URL should point at your backend (http://localhost:8000 if you're following the ports in this guide). Then start the dev server:

pnpm run dev

This serves the SvelteKit app on http://localhost:5173 (vite dev under the hood, per the dev script in frontend/package.json).

Running all three

A full local stack is four long-running processes across (at least) three terminals, all started from the state above:

# Terminal 1: from backend/
uv run python manage.py runserver

# Terminal 2: from backend/
uv run celery -A crm worker --loglevel=INFO

# Terminal 3: from frontend/
pnpm run dev

Redis and PostgreSQL need to already be running before any of these start (either installed locally or run as standalone containers, as shown above). Add a fourth terminal for uv run celery -A crm beat --loglevel=INFO from backend/ if you need scheduled tasks to fire.

Once all of this is up, see First sign-in for how to get an authenticated session, and Demo data and packs for loading sample data.