Skip to content

Environments and deploy

Three environments — dev, staging, production — each fed by a git branch. A push deploys itself.

How this page was verified

Read on 2026-09-15 from each deployable repo's .github/workflows/deploy.yml and deploy/ scripts, and the server facts from the exsport-repos skill.

Branch → environment

EnvBranchStorefrontAdminBackend API
devdevelopmentdev.exsport.bdt.devdev-admin.exsport.bdt.devvia the app hosts
stagingstagingstg.exsportbags.comadmin-stg.exsportbags.comapi-stg.exsportbags.com
prodmainwww.exsportbags.comadmin.exsportbags.comwww.exsportbags.com/gapi/graphql

The webhook repo is single-branch, so it has only production. The production backend path is /gapi (not /be-api); api.exsportbags.com does not resolve. Prod storefront pages are client-rendered, so fetching the HTML shows no prices — query /gapi/graphql (public queries need no auth) or drive a browser to audit live data.

Auto-deploy on push

Every deployable repo (backend, storefront, admin, webhook) has the same deploy.yml:

  • Trigger: push to development, staging or main (webhook: main only).
  • Runner: the self-hosted [self-hosted, rumah] runner — never ubuntu-latest.
  • Concurrency: group deploy-<ref>, cancel-in-progress: false — deploys queue, they do not kill each other.
  • GitHub environment per ref (mainproduction, else the branch name), which lights up the Jira Deployments tab for the DEF07 ticket.
  • Script per branch: developmentdeploy/dev.sh, stagingdeploy/staging.sh, maindeploy/prod.sh. Webhook always runs deploy/prod.sh.
  • Slack notice at the end (if: always()) via deploy/lib/slack-notify.py using repo secret SLACK_WEBHOOK_URL; it no-ops if the secret is unset and never fails the job.

The old d.bdt.dev webhook deployer is disabled for all four repos. Watch a run in the repo's Actions tab, and still confirm the served bundle or API when it matters — a deploy can fail.

Where each environment runs

Observed 2026-08-27; SSH aliases are in ~/.ssh/config.

EnvHostNotes
devone box, rumah.bdt.dev (ubuntu@)apps under /home/ubuntu/apps/, DB is local Postgres exsport_dev
stagingone box, alias ex-stg-cdall four apps under /home/ex-stg/apps/ on PM2 (storefront :3011, admin :3012, backend :3013, webhook :3014), DB exsport_stg. Staging ports differ from the local-Mac ports.
prodsplit hosts behind bastion exsport-prod-bastionexsport-prod-be (backend at /opt/backend, a built artifact with no git checkout), -fe, -webhook, -db, -proxy. Live production — read-only unless Eris says otherwise.

The Pulumi AWS setup

def-exsport-pulumi also defines a full AWS setup (ECS, ALB, RDS, ElastiCache, ECR, S3, SNS) across seven stacks. That is not the same as the serving hosts above — read def-exsport-pulumi/stacks/ before assuming it is the live path. See the Pulumi repo page.

A note on next start

On admin and storefront, typescript is a runtime dependency because next start loads next.config.ts at boot. Keep it in dependencies or prod crashes on start.