Skip to main content

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.

DevelopmentProduction
Hostdevelopment-django-vmDockerApp VM
Path/opt/emaw/emaw-django-appthe stack's checkout, managed by Portainer
Branchdevelopmain
Triggermanual: SSH, git pull, docker compose up -dmanual: Portainer redeploy
AccessTailscale + SSH keyIP whitelist + SSH key; Portainer at https://52.234.34.217:9443/
SettingsEMAW_BACKEND.settings.developmentEMAW_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)​

  1. Get your IP allow-listed by Matt Birne and open https://52.234.34.217:9443/.
  2. Open the EMAW stack. Portainer shows the compose services; the production .env is held in the stack's environment configuration, not in git.
  3. Pull the latest main into the stack's source (Portainer "Pull and redeploy" for git-backed stacks, or SSH to the VM and git pull origin main if the stack is file-based).
  4. Redeploy the stack. The django container runs collectstatic and migrate on start.
  5. Verify: docker compose ps on the VM or the containers view in Portainer; check https://app.emaw-app.com/admin/ loads; tail error.log.
  6. If Qdrant was recreated with an empty volume, run load_bible_embeddings inside the django container.
Order of operations with frontends

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:

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​

  • develop merged to main by pull request; tests green locally
  • Any new env vars added to the production .env in Portainer and to the compose x-app-env anchor
  • Stripe webhook events, payment method configurations and products exist in live mode if commerce changed (see docs/donations-go-live.md for the pattern)
  • Backend deployed, /admin/ reachable, error.log quiet
  • 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