Deploying the dashboard
The internal dashboard (Next.js frontend and Flask API, surfacing BlueSnap, Chargebee, Google Analytics, and Freshdesk data) lives in the private axxya-dashboard repository. It runs as one private ECS task: the Copilot service dashboard in the nexgen-v3 app, prod environment. Nothing listens on the task from the internet. A cloudflared sidecar opens an outbound Cloudflare Tunnel to dashboard.axxya.com, and Cloudflare Access in front of that hostname limits it to an email allow list signed in through Microsoft Entra ID.
This page covers shipping a new build to the existing deployment. The one-time setup (Access application, tunnel, SSM secrets, the report store bucket, copilot svc init) and the teardown steps are in deploy/README.md in the dashboard repository. How the dashboard builds and stores its chart data is on How the dashboard loads its data.
When to run this
- After merging a code change to
mainin the dashboard repository. - After editing
copilot/dashboard/dashboard.env, for example to add or remove someone fromDASHBOARD_ALLOWED_EMAILS. A settings-only change reaches the running task the same way as a code change.
Prerequisites
- Docker Desktop is running. Copilot builds the image locally and pushes it to ECR.
- The AWS CLI and Copilot are authenticated against the nexgen-v3 account (
202077789656). copilot/dashboard/dashboard.envexists on the machine you deploy from. It is gitignored and holds the non-secret settings; create it fromdeploy/dashboard.env.exampleand fill it in from your local.envif it is missing.- Every secret named in
copilot/dashboard/manifest.ymlalready exists in SSM. ECS fetches them in one call, and one missing or mis-tagged parameter stops the task from starting. - The report store bucket
nexgen-v3-dashboard-storeexists anddashboard.envnames it inDASHBOARD_STORE_BUCKET. The task role's access to it comes fromcopilot/dashboard/overrides/cfn.patches.yml, which Copilot applies as part of the deploy; the bucket itself is created by hand (the commands are indeploy/README.md).
The image is built from the files on disk, including uncommitted changes and whatever branch is checked out. Always deploy from an up-to-date main with a clean status.
Steps
- Update the checkout and confirm nothing unexpected is in it:
cd ~/git/axxya/dashboard
git pull
git status
- Optional: check for build and type errors locally before spending a deploy on them:
cd frontend && npm run build && cd ..
docker build --platform linux/amd64 -t axxya-dashboard:local .
- Confirm you are in the right AWS account. The output must show account
202077789656:
aws sts get-caller-identity
- Build, push, and deploy:
copilot svc deploy --name dashboard --env prod
The deploy takes several minutes. The new task serves the chart data stored in the bucket as soon as it is up and rebuilds it in the background, so charts open at once after a deploy and show the time their data was really built. Only the very first deploy, into an empty bucket, still builds each chart on request until the startup run finishes (a minute or two).
Verifying
Check that the new task is running and healthy, that both processes and the tunnel came up, and that the site is still behind the Access login:
copilot svc status --name dashboard --env prod
copilot svc logs --name dashboard --env prod --since 15m | grep -iE "Registered tunnel connection|auth denied|Ready in|Listening"
curl -s -o /dev/null -w "%{http_code}\n" https://dashboard.axxya.com/
In the logs, expect gunicorn Listening at: http://127.0.0.1:5050, Next.js Ready in ..., four Registered tunnel connection lines, and report store backend=s3 location=nexgen-v3-dashboard-store followed a little later by report refresh finished. The tunnel shows as Healthy under Zero Trust > Networks > Tunnels.
The bucket should then hold a fresh object per report:
aws s3 ls s3://nexgen-v3-dashboard-store/reports/ --recursive --human-readable | tail
aws s3 cp s3://nexgen-v3-dashboard-store/status.json -
The curl from a terminal with no Cloudflare session must return 302 (a redirect to axxya.cloudflareaccess.com). A 200 means the hostname is exposed without Access in front of it; stop and fix the Access application before doing anything else.
Then open https://dashboard.axxya.com in a private browser window, sign in with Microsoft, and confirm the charts load with data.
Changing who has access
- Add or remove the address in the
Dashboard allowed userslist in Cloudflare Zero Trust (Reusable components > Lists). - Make the same change to
DASHBOARD_ALLOWED_EMAILSincopilot/dashboard/dashboard.envand redeploy with the command in step 4 above. Flask checks the allow list on every API request, so the two lists must match. - When removing someone, also revoke their session under Zero Trust > My Team > Users.
Troubleshooting
- The task never becomes healthy after a deploy. Run
copilot svc logs --name dashboard --env prod --follow. A secret missing from SSM stops the task before it starts, and the ECS event names the parameter. The container health check calls/api/dev/healththrough the Next.js proxy, so it fails if either Next.js or Flask is down. - The page loads but the charts show "Access denied". Cloudflare let the person in but Flask did not. Look for an
auth deniedline in the logs: "invalid Cloudflare Access token" meansCF_ACCESS_AUDorCF_ACCESS_TEAM_DOMAINindashboard.envdoes not match the Access application, and "is not on the allow list" means the address is missing fromDASHBOARD_ALLOWED_EMAILS. - Some visitors see a connection error or a different machine's dashboard. The tunnel has more than one connector. It must have exactly one, the ECS task. If the
cloudflaredinstall command was ever run on a Mac, remove that connector withsudo cloudflared service uninstall. - Docker errors during the deploy. The image is built for
linux/amd64. Make sure Docker Desktop is running and has enough disk space, then rerun the deploy. - Charts are slow or show old data after the deploy. See the troubleshooting list on How the dashboard loads its data;
report store backend=fileorAccessDeniedin the logs means the bucket setting or the task role override is missing.
Related
- How the dashboard loads its data, for the data sources, the report store and the background refresh.
README.mdanddeploy/README.mdin the dashboard repository, which cover local development, the one-time Cloudflare, SSM and bucket setup, and removing the deployment.- Nexgen V3 deployment, for the other services in the same Copilot app.