GitHub

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 off the selector: grep -oE ‘<MenuItem value=“[^”]+“>[^<]+<’ DatabaseSelect.tsx is all uppercase.

  1. 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.
  2. 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.
  3. 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 client field. Must match EXACTLY.

          1. List every client Sanity knows about:
          2. Find in the list.

          Not in the list -> no Sanity client exists yet. Exit and ask ME.

    • Verficiation: Check if key + fields are in the file.
  4. If == staging, PR and Deploy. Between staging and production, this only needs to be done once.

    • Verification frontend/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 becomes thehelperbees_com — 17 characters. With the template’s docker_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

  1. Create a branch and commit the tfvars changes
  2. Open a PR targeting the plan branch
  3. After PR approval, merge into plan
  4. 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.

Post-Launch Verification

Edit this page