Deployment Unknowns & Prerequisites
Things that need to be sourced, provisioned, or decided before a production deployment can go live. None of these exist in the codebase — they were held by the third party and were never handed over.
External services (credentials needed)
These are live third-party integrations the app calls at runtime. Without valid credentials the relevant features will fail silently or error.
| Service | What it does | Env var(s) needed | Status |
|---|---|---|---|
| SendGrid | Transactional email | SENDGRID_API_KEY | ✅ Account and key provided by third party — variable entry only |
| Google reCAPTCHA v3 | Contact form bot protection | DJANGO_GOOGLE_RECAPTCHA_TOKEN | ❓ Need site secret |
| Clear Checks | Applicant background checks | CLEARCHECKS_URL, CLEARCHECKS_API_KEY | ⚠️ Marked deprecated in code (v2.1.0) — confirm if still needed |
SendGrid — remaining steps
Credentials are in hand. Before go-live:
- Update
DEFAULT_FROM_EMAILaway fromMedical Web Experts <devops@medicalwebexperts.com>to a Nova Home Care address - Verify the sending domain in SendGrid (add SPF + DKIM DNS records)
- Set
SENDGRID_API_KEYin the production secrets store
AWS resources (to be provisioned)
These don't exist yet and need to be created in the NHC AWS account.
S3 — two buckets required
The Django backend uses S3 for both static files and user-uploaded media. Two separate buckets are expected:
| Bucket | Purpose | ACL |
|---|---|---|
| Public bucket | collectstatic output, public uploads | public-read for static prefix |
| Private bucket | Patient/employee documents, PDFs | private — access via presigned URLs |
Env vars: DJANGO_AWS_S3_PUBLIC_BUCKET_NAME, DJANGO_AWS_S3_PRIVATE_BUCKET_NAME, DJANGO_AWS_S3_REGION_NAME, DJANGO_AWS_ACCESS_KEY_ID, DJANGO_AWS_SECRET_ACCESS_KEY
An optional CloudFront distribution can front the public bucket: DJANGO_AWS_S3_CUSTOM_DOMAIN
IAM
A dedicated IAM user or role needs S3 read/write on both buckets. If running on EC2 or ECS, prefer an instance role / task role over long-lived access keys.
The signing certificate (nova.p12)
The app uses pyHanko to digitally sign PDF documents. It expects:
- A
.p12file atsecrets/nova.p12inside the container - The certificate password in
PYHANKO_P12_PASSWORD
This is a hard blocker — without it, the app starts and logs in normally but PDF signing fails silently. A user submits a form, the document stays stuck in a pending state indefinitely, and the Celery worker logs the error. No crash, no obvious alert — it just never completes.
Step 1 — identify what type of cert was used
Download any PDF that was signed on the third party's staging environment. Open it in Adobe Acrobat Reader and look at the signature panel:
| What Adobe shows | Certificate type |
|---|---|
| Green checkmark, "Certified by..." with a known issuer | Commercial cert (DigiCert, GlobalSign, Sectigo, etc.) |
| Yellow warning, "The identity of the signer is unknown" | Self-signed |
If you cannot get a signed PDF from the third party's environment, assume self-signed and proceed to Step 3.
Step 2 — request the certificate from the third party
The cert was generated for Nova Home Care — it belongs to the client, not the developer. Request:
- The
nova.p12file - The password used to protect it (
PYHANKO_P12_PASSWORD)
If they cooperate, skip to Step 4.
Step 3 — if they won't or can't provide it
If self-signed — generate a replacement. There is no loss of functionality. New documents will be signed with the new cert; existing signed documents remain valid against the old cert (the public key is embedded in each PDF at signing time).
# Run on any machine with OpenSSL installed
openssl req -x509 -newkey rsa:2048 -keyout nova.key -out nova.crt -days 3650 -nodes \
-subj "/CN=Nova Home Care/O=Nova Home Care/C=US"
# It will prompt for an export password — set one and record it as PYHANKO_P12_PASSWORD
openssl pkcs12 -export -out nova.p12 -inkey nova.key -in nova.crt \
-name "Nova Home Care Signing"
# Clean up — only nova.p12 is needed going forward
rm nova.key nova.crtIf commercial — purchase a new document signing certificate. The new cert will have a different issuer fingerprint but the app does not care — pyHanko only needs a valid .p12. Existing signed PDFs are unaffected.
Recommended issuers (all support PKCS12 / .p12 export):
| Issuer | Approximate cost |
|---|---|
| Sectigo (Comodo) | ~$100/year |
| DigiCert | ~$200/year |
| GlobalSign | ~$150/year |
Step 4 — deploy the certificate
Never commit nova.p12 to the repo. In production, store it as a binary secret and mount it at runtime:
# Copy to the secrets directory on the EC2 instance
sudo mkdir -p /opt/nhc-portals/django-api/secrets
sudo cp nova.p12 /opt/nhc-portals/django-api/secrets/nova.p12
sudo chmod 600 /opt/nhc-portals/django-api/secrets/nova.p12Set PYHANKO_P12_PASSWORD in django-api/.env to the certificate password.
Verify it works by running the PDF smoke test in the Smoke Tests checklist before signing off on the migration.
Frontend — what replaces Netlify
The portals .env.development.example references https://develop--novahomecaresite.netlify.app — that's the third party's Netlify deployment. It needs to be replaced.
In production the React/Vite app is a static build (not a dev server). Options:
- S3 + CloudFront — natural fit since S3 is already used for Django storage
- Cloudflare Pages — already proven for this org (docs are deployed this way)
VITE_SITE_BASE_URL must be updated to the new domain once decided.
Django secret key
DJANGO_SECRET_KEY must be a long random string unique to production. Never reuse the development value. Generate one:
python -c "import secrets; print(secrets.token_urlsafe(50))"ALLOWED_HOSTS and CORS origins
Once the production domain is known, set:
DJANGO_ALLOWED_HOSTS— the API domain (e.g.api.novavirtual.site)DJANGO_ALLOWED_ORIGINS— the frontend domain (e.g.https://app.novavirtual.site)
The EC2 private IP is auto-detected from the EC2 metadata endpoint and added to ALLOWED_HOSTS automatically for ALB health checks — no manual config needed for that.
Organization tokens
Three org tokens are hardcoded across the codebase and in the testing .env:
| Token | Org |
|---|---|
5ae807a7-df99-4bc2-8d51-fa808b81a41e | Nova Health First, Inc |
e9686292-0b70-4019-9ef1-c1a275a9723d | Essential Home Health Care |
9f66eef3-be72-482b-a6a9-c1eb647367f8 | Aurora Home Health (also in DJANGO_AURORA_ORG_TOKEN) |
These tokens are set in the database via fixtures. Confirm the production database is loaded from the same fixtures and these tokens match.
Summary checklist
- [x] SendGrid — account and key provided, variable entry only
- [ ] SendGrid — update sender identity away from
medicalwebexperts.com - [ ] SendGrid — verify sending domain (SPF + DKIM)
- [ ] Google reCAPTCHA v3 site key + secret
- [ ]
nova.p12— request from third party (belongs to Nova HC); regenerate if unavailable - [ ] S3 public bucket created
- [ ] S3 private bucket created
- [ ] IAM role/user with S3 access
- [ ] Django secret key generated for production
- [ ] Decision on frontend hosting (S3+CloudFront or Cloudflare Pages)
- [ ] Production domains decided (
DJANGO_ALLOWED_HOSTS,DJANGO_ALLOWED_ORIGINS,VITE_SITE_BASE_URL) - [ ] Clear Checks — confirm deprecated and disable, or provide sandbox/prod key
- [ ] Database loaded with correct fixtures and org tokens verified