Deployment
There is no continuous deployment for the backend. Dev is deployed by pulling on the VM; production through Portainer. The two GitHub/Azure workflows in the repo target a legacy App Service and are not the live backend.
| Development | Production | |
|---|---|---|
| Host | development-django-vm | DockerApp VM |
| Path | /opt/emaw/emaw-django-app | the stack's checkout, managed by Portainer |
| Branch | develop | main |
| Trigger | manual: SSH, git pull, docker compose up -d | manual: Portainer redeploy |
| Access | Tailscale + SSH key | IP whitelist + SSH key; Portainer at https://52.234.34.217:9443/ |
| Settings | EMAW_BACKEND.settings.development | EMAW_BACKEND.settings.production |
Development
ssh <you>@<development-django-vm>
cd /opt/emaw/emaw-django-app
git pull origin develop
docker compose up -d --build # --build when requirements or the Dockerfile changed
docker compose ps
docker compose logs -f django # watch migrate and gunicorn come up
Migrations run automatically on container start. If a migration fails the django container restarts in a loop; fix forward or revert the merge, then redeploy.
If the VM's stack was stopped because someone took the tunnel, this is the same procedure they use to hand it back (Local development).
Production (Portainer)
- Get your IP allow-listed by Matt Birne and open
https://52.234.34.217:9443/. - Open the EMAW stack. Portainer shows the compose services; the production
.envis held in the stack's environment configuration, not in git. - Pull the latest
maininto the stack's source (Portainer "Pull and redeploy" for git-backed stacks, or SSH to the VM andgit pull origin mainif the stack is file-based). - Redeploy the stack. The
djangocontainer runscollectstaticandmigrateon start. - Verify:
docker compose pson the VM or the containers view in Portainer; checkhttps://app.emaw-app.com/admin/loads; tailerror.log. - If Qdrant was recreated with an empty volume, run
load_bible_embeddingsinside thedjangocontainer.
When a change spans the backend and a frontend (for example the hand-off contract), deploy backend first, then the web app, then the website. The frontends are backwards compatible with the previous backend for one release; the backend is not forward compatible with new frontend expectations.
Rollback
Redeploy the previous commit of main the same way. Migrations are not automatically reversed; if a release added a destructive migration, roll forward with a fix instead.
Legacy workflows (deprecated)
Two files still deploy to the Azure Web App EMAWWeb on every push to main:
.github/workflows/main_emawweb.yml("Build and deploy Python app to Azure Web App - EMAWWeb"), Python 3.10,azure/login@v2+azure/webapps-deploy@v3.azure-pipelines.yml, Python 3.11,AzureWebApp@1.
They predate the Docker stack, use a different Python than the 3.12 image, run no tests, and do not touch the VM. They are harmless but misleading, and CLAUDE.md's CI section still describes them as the deployment. Removing them (or the EMAWWeb App Service) is on the known gaps list.
Release checklist
-
developmerged tomainby pull request; tests green locally - Any new env vars added to the production
.envin Portainer and to the composex-app-envanchor - Stripe webhook events, payment method configurations and products exist in live mode if commerce changed (see
docs/donations-go-live.mdfor the pattern) - Backend deployed,
/admin/reachable,error.logquiet - Web app, then website, then CRM deployed if they depend on the change
- Smoke test: sign in on the web app, load the CRM contacts list, place a small store order if commerce changed