Launching a Benefits Hub Tenant
Step-by-step guide to launch a new Benefits Hub tenant on the shared Benefits Hub VM.
Prerequisites
Before starting a launch, confirm the following:
The HA platform and tenant must already be launched. Benefits Hub depends on the Django HomeAlign backend for its API and authentication. If the HA tenant doesn’t exist yet, follow the HA Admin Portal launch runbook first.
Phase 1: Setup Partner in Django HomeAlign
Find
Run the Tenant Database Script
- ./add_new_tenant_database.sh UAT database name e.g. hAScanUAT
Production database name e.g. hAScan database alias e.g. scan <-
becomes
display name e.g. Scan <- the dropdown label database user default haadmin is correct prod / staging DB host defaults are correct Verification: Review the diff PR 861. Expect exactly 4 files. NOTE: If doing this for= production, most likey this step has already been done.
- ./add_new_tenant_database.sh UAT database name e.g. hAScanUAT
Production database name e.g. hAScan database alias e.g. scan <-
becomes
Add
_WEB_DB_PASS = staging ==> project prj-bu1-n-vault-secrets-9d03 = production ==> project prj-bu1-p-vault-secrets-8d4a Two vaults per environment: django-ha-vault and eligibility-service-vault. - Add the key
_WEB_DB_PASS to both. Verification- Four places: {django-ha-vault, eligibility-service-vault} x
{staging, production}
= staging ==> project prj-bu1-n-vault-secrets-9d03 = production ==> project prj-bu1-p-vault-secrets-8d4a - Each vault is ONE secret holding all tenants’ keys, so check for the
KEY
_WEB_DB_PASS inside the payload. PASS = the key exists and is non-empty in all four.
- Four places: {django-ha-vault, eligibility-service-vault} x
{staging, production}
- Add the key
Add the tenant to config/client_config.json
- Add the following key “<KEY_NAME>” : { “ha_db_name_key”:
“
”, “client_id”: } If Sanity is required feature “cp_tenants”: { “<cp_key>”: { “sanity_client”: { “uat”: { “sanity_client_name”: “
” }, “production”: { “sanity_client_name”: “ ” } } } } <cp_key>: find in consumer_portal/.envs/.
- /.web No .envs dir for the tenant means Consumer Portal does not have it, which also means it has no Sanity content. : the tenant’s value in Sanity’s clientfield. Must match EXACTLY.- List every client Sanity knows about:
- Find
in the list.
Not in the list -> no Sanity client exists yet.
Exitand askME.
Verficiation: Check if key + fields are in the file.
- Add the following key “<KEY_NAME>” : { “ha_db_name_key”:
“
If
== staging, PR and Deploy. Between staging and production, this only needs to be done once. Verificationfrontend/src/layout/DatabaseSelect.tsx — one line:
Phase 2: Add App Config in Infrahive
Benefits Hub tenants share a single VM. Launching a new tenant means
adding an app entry to the hb-infra tfvars files, which
provisions Cloudflare DNS records, tunnels, and Ansible deployment
configuration.
Step 3: Add App Entry to Non-Production
Edit infra/hb-infra/non-production.auto.tfvars and add a
new entry to the apps list:
{
name = "<TENANT>-benefits-platform"
ansible_name = "<TENANT>-benefits-platform"
vm_resource_name = "benefits-hub"
db_resource_name = ""
public_container_name = "ui"
admin_container_name = "ui"
github_repo = "git@github.com:thehelperbees/benefits-platform.git"
github_short_repo = "benefits-platform"
docker_container_abbrv = "benefits-platform"
docker_image = "benefits-platform/ui"
docker_stack_name = "<TENANT>_benefits_platform"
docker_compose_version = "3.9"
docker_secret_file = ""
ping_route = "/api/ping"
app_env = "<TENANT>-staging"
dashboard_name = "<TENANT_DISPLAY> Benefits Platform"
domains = [
{
name = "thb.sh"
cloudflare_zone_id = "<NON_PROD_ZONE_ID>"
subdomains = [
{
"type" : "admin"
"name" : "<TENANT>benefits"
"access_policies" : ["thb_email", "vpn_bypass"]
},
]
}
]
public_site_urls = []
admin_site_urls = ["<TENANT>benefits.thb.sh"]
enable_ws : false
enable_gql : false
enable_flower : false
enable_camunda : false
},
Step 4: Add App Entry to Production
Edit infra/hb-infra/production.auto.tfvars with the
production variant. Key differences from non-production:
{
name = "<TENANT>-benefits-platform"
ansible_name = "<TENANT>-benefits-platform"
vm_resource_name = "benefits-hub"
db_resource_name = ""
public_container_name = "ui"
admin_container_name = "ui"
github_repo = "git@github.com:thehelperbees/benefits-platform.git"
github_short_repo = "benefits-platform"
docker_container_abbrv = "benefits-platform"
docker_image = "benefits-platform/ui"
docker_stack_name = "<TENANT>_benefits_platform"
docker_compose_version = "3.9"
ping_route = "/api/ping"
app_env = "<TENANT>-production"
dashboard_name = "<TENANT_DISPLAY> Benefits Platform"
domains = [
{
name = "thb.nu"
cloudflare_zone_id = "<PROD_ZONE_ID_THB_NU>"
subdomains = [
{
"type" : "public"
"name" : "<TENANT>benefits"
"access_policies" : []
}
]
},
{
name = "thehelperbees.com"
cloudflare_zone_id = "<PROD_ZONE_ID_THB_COM>"
subdomains = [
{
"type" : "public"
"name" : "<TENANT>benefits"
"access_policies" : []
}
]
}
]
public_site_urls = ["<TENANT>benefits.thehelperbees.com"]
admin_site_urls = ["<TENANT>benefits.thb.nu"]
enable_ws : false
enable_gql : false
enable_flower : false
enable_camunda : false
},
Remember to ./zig/zig build fmt to ensure the tfvars
file is properly formatted.
Production only — check the length of
docker_stack_name.Docker caps config and secret names at 64 characters. The deploy builds each name as:
<docker_stack_name>_cf_<domain with . as _>_config_<5-char hash>Production’s longest domain is
thehelperbees.com, which becomesthehelperbees_com— 17 characters. With the template’sdocker_stack_name = "<tenant>_benefits_platform":len(<tenant>) + 18 + 4 + 17 + 5 + 8 must be <= 64 ==> len(<tenant>) must be <= 12
Fields that change per tenant:
| Field | Pattern |
|---|---|
name / ansible_name |
<tenant>-benefits-platform |
docker_stack_name |
<tenant>_benefits_platform (underscores) |
app_env |
<tenant>-staging (non-prod) /
<tenant>-production (prod) |
dashboard_name |
<TenantDisplay> Benefits Platform |
domains / URLs |
Subdomain is <tenant>benefits (no separator) |
Non-prod vs prod differences:
| Aspect | Non-production | Production |
|---|---|---|
| Domain(s) | thb.sh |
thb.nu + thehelperbees.com |
| Subdomain type | "admin" |
"public" |
| Access policies | ["thb_email", "vpn_bypass"] |
[] (open access) |
docker_secret_file |
"" (present) |
absent |
public_site_urls |
[] |
["<tenant>benefits.thehelperbees.com"] |
admin_site_urls |
["<tenant>benefits.thb.sh"] |
["<tenant>benefits.thb.nu"] |
Convention: Copy the Cloudflare zone IDs from an existing benefits-platform entry in the same tfvars file.
Phase 3: Release and Build Infrastructure
Step 5: Create a PR and Deploy
- Create a branch and commit the tfvars changes
- Open a PR targeting the
planbranch - After PR approval, merge into
plan - Deploy a release
If you are deploying a whole batch of tenants, do so in one big staging batch, and then later, one big production batch.
What gets provisioned:
- Cloudflare tunnel routes and DNS records for the new subdomain(s)
- Cloudflare Access policies (non-production only)
- GCP Secret Manager vault for the app
- Ansible config YAML uploaded to GCS bucket (consumed by AWX for deployment)
- GCP uptime monitoring (production only)
Verify: use Main branch.
Read the hosts from the tenant’s entry in tfvars:
public_site_urls[] and admin_site_urls[].
These are complete FQDNs. Do not assemble them from
domains[] / subdomains[]. Production has two
zones and staging has one, so the number of hosts differs by environment
— check every URL listed.
For each host:
curl -sS -o /dev/null -m 15 -w '%{http_code}\n' https://<host>000, or DNS does not resolve — Error, Terraform never applied- any HTTP status — pass, DNS and the tunnel config exist
If That Fails: Open Cloud
Build History and confirm the hb-infra apply completed
successfully for each target environment.
Phase 4: Setup Benefits-Platform Environment
Step 6: Add Environment Variables
In the benefits-platform
repo, create .env files for the new tenant. Each tenant
needs two files:
.envs/.<tenant>-staging/.web.envs/.<tenant>-production/.web
Staging template
(.envs/.<tenant>-staging/.web):
API_BASE_URL=https://homealign-dj.thb.sh/core-api
TENANT_LOGIN_URL=https://homealign-dj.thb.sh/tenant-login/
TENANT_REFRESH_URL=https://homealign-dj.thb.sh/tenant-refresh/
IDENTITY_SERVER_URL=https://auth.myhaapp.com
TENANT=<tenant>
POSTHOG_DEBUG=false
POSTHOG_HOST=https://us.i.posthog.com
POSTHOG_KEY=<copy from existing tenant>
SUPPORT_URL=https://<tenant>-thb.zendesk.com/hc/en-us/requests/new
Production template
(.envs/.<tenant>-production/.web):
API_BASE_URL=https://homealign-dj.thehelperbees.com/core-api
TENANT_LOGIN_URL=https://homealign-dj.thehelperbees.com/tenant-login/
TENANT_REFRESH_URL=https://homealign-dj.thehelperbees.com/tenant-refresh/
IDENTITY_SERVER_URL=https://auth.myhomealign.com
TENANT=<tenant>
POSTHOG_DEBUG=false
POSTHOG_HOST=https://us.i.posthog.com
POSTHOG_KEY=<copy from existing tenant>
SUPPORT_URL=https://<tenant>-thb.zendesk.com/hc/en-us/requests/new
Only TENANT and SUPPORT_URL are
tenant-specific. Copy POSTHOG_KEY from any existing
tenant’s .env file. All other values are constant per
environment.
To make the new tenant accessible on the admin benefits hub, add it to
AVAILABLE_TENANTS in
.envs/.admin-production/.web.envs/.admin-staging/.web
Create a PR in the benefits-platform repo. See this PR for a reference example.
Phase 5: Add App to AWX Deployment
Step 7: Update AWX Job Template
Add <tenant>-benefits-platform to the AWX job
template survey’s app list dropdown for the Benefits Platform deployment
job. This is a manual step in the AWX UI.
Step 8: Deploy via AWX
Trigger a deployment via AWX for the new tenant in each environment. Deploy to staging first, verify, then deploy to production.