Local development
The recommended way to run the backend is the full Docker stack with the dev tunnel token, which makes your laptop the dev environment: the dev website, admin CRM and web app all hit your code. The alternative is running Django on the host for breakpoints. Both talk to the shared dev database over Tailscale.
Prerequisites: everything in Workstation setup (Backend section), Tailscale connected, the backend .env in the repo root, and an SSH user on the dev VM.
1. Get the code
git clone https://github.com/Every-Man-A-Warrior/emaw-django-app.git
cd emaw-django-app
git checkout develop
py -3.12 -m venv .venv
.venv\Scripts\python.exe -m pip install -r requirements.txt
Copy the .env you were given into the repo root. It is a copy of the dev environment's configuration, so it works as-is inside compose (REDIS_URL=redis://redis:6379/0, QDRANT_HOST=qdrant).
Always use .venv\Scripts\python.exe for manage.py and pytest.
2. Take over the dev tunnel
The dev TUNNEL_TOKEN in .env is the same one the dev VM uses. If both run at once, Cloudflare splits requests between the two connectors and you cannot tell which served what. Stop the server's stack first.
ssh <you>@<development-django-vm>
cd /opt/emaw/emaw-django-app
docker compose down
exit
Tell the team channel you have the tunnel.
3. Start locally
Docker Desktop and Tailscale running, then:
docker compose up --build
First start builds the image. Within a minute dev.emaw-app.com is your machine. Check with docker compose ps that all six containers are up.
The django container runs migrate on every start. If your branch adds migrations, starting the stack applies them to the dev database for everyone. Coordinate before bringing up a branch with schema changes, and never leave a half-applied migration behind.
First run only: load AI search data
Your Qdrant starts empty. Load the NIV embeddings once (about 500 MB from blob storage):
docker compose exec django python manage.py load_bible_embeddings
Until then POST /rest/bibles/search/ returns nothing; everything else works. The data persists in the qdrant_storage and embeddings_data volumes until docker compose down -v. Details: qdrant_storage/README.md.
The per-version SQLite Bibles are committed under bibles/storage/ and need no setup.
4. Hand the tunnel back
When you are done, or at the end of the day:
docker compose down
ssh <you>@<development-django-vm>
cd /opt/emaw/emaw-django-app
git pull origin develop
docker compose up -d
docker compose ps
git pull origin develop is how new code reaches the dev server; there is no CI for the backend. If your work is merged, the VM now runs it.
Running Django on the host (debugging)
For breakpoints and auto-reload, run only Redis and Qdrant in Docker and point the host at them. These override the compose hostnames in .env for this shell only:
docker compose up -d redis qdrant
$env:REDIS_URL = "redis://localhost:6379/0"
$env:QDRANT_HOST = "localhost"
$env:QDRANT_PORT = "7002"
$env:DB_HOST = "<tailscale ip from .env>" # if .env has the compose-internal hostname
.venv\Scripts\python.exe manage.py runserver
Compose does not publish Redis. Add ports: ["127.0.0.1:6379:6379"] to the redis service locally (do not commit it), or Redis-backed features fall back: caching and throttles fail open, but Celery tasks will not run. For Celery on the host:
.venv\Scripts\python.exe -m celery -A EMAW_BACKEND worker -l info --pool=solo
In this mode the tunnel is not running, so the frontends cannot reach you; use http://localhost:8000 from a local frontend build instead (the development CORS list allows the usual Vite ports).
Stripe webhooks locally
Stripe needs to reach your stack. Through the tunnel it already can (webhooks are configured for dev.emaw-app.com). For host-run Django, bridge with the Stripe CLI and put the printed whsec_ into STRIPE_WEBHOOK_SECRET; see payments/README.md.
Useful management commands
docker compose exec django python manage.py createsuperuser
docker compose exec django python manage.py activate_user --user <id>
docker compose exec django python manage.py import_data # curriculum from book_info.json
docker compose exec django python manage.py seed_store_catalog
docker compose exec django python manage.py deposco_check
Full list: Management commands.
Day-to-day checklist
- Tailscale connected (
tailscale status) - Docker Desktop running (restart it if containers cannot reach the DB)
- Nobody else has the tunnel
-
git pull origin developbefore you start a branch - Tests pass against the throwaway Postgres (Testing)
- Tunnel handed back and the VM's stack restarted