Deployment
Deployment is Docker Compose driven from webapp/docker-compose.yml. There is no
separate orchestrator; you build the images that changed, recreate the affected
services, and verify health. This page covers the operations you actually run.
Deploying a change
Which command you run depends on what changed. The backend hot-reloads from a bind mount, the frontend is a baked image — see Docker & local setup for the full model.
Frontend change → rebuild the image
The frontend source is not mounted; it is compiled into dist/ at build time
(Bun build → Node runtime) and PUBLIC_* env vars are inlined into the client
bundle during the build. So any UI change or any change to a PUBLIC_* value
requires a rebuild:
docker compose build frontenddocker compose up -d frontendBackend code change → usually nothing
./backend is bind-mounted and uvicorn --reload restarts the app in place, so
editing Python on the host is picked up automatically. Rebuild only when
dependencies (pyproject.toml) or the Dockerfile change:
docker compose build backenddocker compose up -d backendNew / changed environment values → recreate the backend
Environment variables are read once at process start. Editing the root .env
does nothing to a running container. Recreate the service so Compose re-reads
env_file and re-interpolates the environment: block:
docker compose up -d --force-recreate backendRecreate frontend (and worker) the same way if you changed variables they
consume. Remember the backend seeds each integration’s Mongo config from env on
first startup only; if a config document already exists it is not overwritten,
so changing a credential in .env after the fact means updating it in the UI /
Mongo, not just recreating the container.
MongoDB persistence
MongoDB is the system of record. Its data lives in the named volume
mongodb_data mounted at /data/db:
volumes: - mongodb_data:/data/db - ./mongodb-init:/docker-entrypoint-initdb.dConsequences:
- The volume survives
docker compose down,up, image rebuilds, and host reboots. Container churn does not lose data. docker compose down -vdestroysmongodb_data(andpostgres_data,backend_uploads,pgadmin_data). Do not run it against a live environment.- Uploaded files persist separately in
backend_uploads(/app/uploads); the Intune/Entra certificate is mounted read-only from the hostcerts/directory.
Back up by snapshotting the volume or running mongodump against the container:
docker compose exec mongodb mongodump --archive=/data/db/backup.archive \ --username "$MONGO_ROOT_USERNAME" --authenticationDatabase admindocker compose cp mongodb:/data/db/backup.archive ./backup-$(date +%F).archiveDisk hygiene
Reclaim space regularly:
# Remove dangling (untagged) images — safe, everyday cleanupdocker image prune -f
# Reclaim build cache (can be large after many rebuilds)docker builder prune -f
# Check what Docker is usingdocker system dfPrune images/build-cache, not volumes. Avoid docker system prune -a --volumes
on a production host — it will remove the data volumes. Stick to docker image prune / docker builder prune.
Health checks
The backend defines a Compose healthcheck that curls its own endpoint:
healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 10s retries: 3GET /health returns {"status": "healthy", "environment": ..., "database": ...}.
Postgres has a pg_isready healthcheck and the worker waits on it
(depends_on: condition: service_healthy).
Verify a deployment:
# Service + health statusdocker compose ps
# Backend health directlycurl -f http://localhost:8000/health
# Frontend is servingcurl -I http://localhost:4321
# Watch logs for startup / sync errorsdocker compose logs -f backendOn backend startup the logs show each integration config being seeded or skipped
(... config already exists ... skipping seed), which is a quick confirmation the
env wiring reached the container. If a sync is stuck “running” after a restart, see
the durability discussion in
ConnectWise Automate scoping & durable workflows.