Deploy
Deploy
Section titled “Deploy”Build the release image from the repository root:
docker build -t ab-test-research-designer:1.0.0 -t ab-test-research-designer:latest .docker inspect ab-test-research-designer:1.0.0 --format '{{.Size}}'Pulling from GHCR
Section titled “Pulling from GHCR”docker pull ghcr.io/brownjuly2003-code/ab-test-research-designer:latestdocker run --rm -p 8008:8008 ghcr.io/brownjuly2003-code/ab-test-research-designer:latestAutomated publish via GitHub Actions
Section titled “Automated publish via GitHub Actions”.github/workflows/docker-publish.yml publishes the multi-arch image (linux/amd64, linux/arm64) to ghcr.io/brownjuly2003-code/ab-test-research-designer on every pushed tag matching v*. The same workflow also supports manual workflow_dispatch: provide a specific tag or leave the input empty to republish the latest v* tag already present in git.
First GHCR release checklist:
- Push a release tag such as
v1.1.0. - Wait for the
Publish Docker imageworkflow to finish successfully. - Open https://github.com/brownjuly2003-code/ab-test-research-designer/pkgs/container/ab-test-research-designer, go to
Settings, and switch package visibility to Public. - Verify anonymous pull from a clean machine:
docker pull ghcr.io/brownjuly2003-code/ab-test-research-designer:v1.1.0Notes:
- GHCR creates the package as private on the first push even when it is linked to the repository, so the visibility switch is a one-time manual step.
- The workflow authenticates with the repository
GITHUB_TOKEN; no personal access token is required. - Build cache uses
type=gha; GitHub may evict it after about 7 days of inactivity, so the first build after an idle period can be slower.
Tag For Registry
Section titled “Tag For Registry”Set <REGISTRY> explicitly for your target registry namespace, for example ghcr.io/<owner> or docker.io/<user>.
docker tag ab-test-research-designer:1.0.0 <REGISTRY>/ab-test-research-designer:1.0.0docker tag ab-test-research-designer:latest <REGISTRY>/ab-test-research-designer:latestDo not run push until registry credentials, target namespace, and image scan are ready.
docker push <REGISTRY>/ab-test-research-designer:1.0.0docker push <REGISTRY>/ab-test-research-designer:latestRun Locally
Section titled “Run Locally”Open mode:
docker run --rm --name ab-test-v1-open -p 8008:8008 ab-test-research-designer:1.0.0Secure mode:
docker run --rm --name ab-test-v1-secure -e AB_API_TOKEN=replace-with-a-write-token -p 8008:8008 ab-test-research-designer:1.0.0Dual-token mode:
docker run --rm --name ab-test-v1-dual -e AB_API_TOKEN=replace-with-a-write-token -e AB_READONLY_API_TOKEN=replace-with-a-readonly-token -p 8008:8008 ab-test-research-designer:1.0.0Signed workspace backup mode:
docker run --rm --name ab-test-v1-signed -e AB_WORKSPACE_SIGNING_KEY=replace-with-a-long-random-secret -p 8008:8008 ab-test-research-designer:1.0.0Slack App mode:
docker run --rm --name ab-test-v1-slack ^ -e AB_SLACK_CLIENT_ID=... ^ -e AB_SLACK_CLIENT_SECRET=... ^ -e AB_SLACK_SIGNING_SECRET=... ^ -p 8008:8008 ab-test-research-designer:1.0.0Create the app from slack/app-manifest.yml, replace {DEPLOY_HOST} with the public HTTPS host, then open /slack/install. Rotate Slack credentials in the Slack App configuration, update runtime secrets, restart the backend, delete the affected slack_installations row if the bot token is being replaced, and reinstall the app.
Hugging Face Spaces Deploy (active hosted demo)
Section titled “Hugging Face Spaces Deploy (active hosted demo)”The current production demo lives on Hugging Face Spaces on the free CPU tier — no credit card required, Docker SDK, always-on.
Live URL: https://liovina-ab-test-research-designer.hf.space
Operator mode (?admin=1): the public app shows only the planning wizard. All
operator surfaces — the saved-project sidebar (projects, history, revisions,
archived, workspace backup) and the System / API keys tabs (backend
status, API session token, AB_ADMIN_TOKEN, Slack App, diagnostics, audit log,
webhooks, raw endpoints) — are hidden and live behind admin mode. Open
…hf.space/?admin=1 to reveal them (persisted to localStorage['ab-test:admin'];
clear with ?admin=0). Logic: app/frontend/src/lib/adminMode.ts. The smoke
flows (app/frontend/src/test/e2e-smoke.spec.ts, scripts/run_local_smoke.py)
open /?admin=1 so they can exercise those surfaces.
Initial setup (one-time):
- Generate a write-scoped token at https://huggingface.co/settings/tokens
- Authenticate:
hf auth login --token <HF_TOKEN> --add-to-git-credential - Create the Space via API:
from huggingface_hub import create_repocreate_repo( repo_id="liovina/ab-test-research-designer", repo_type="space", space_sdk="docker", private=False,)Required README frontmatter (already landed in README.md):
---title: AB Test Research Designeremoji: 🧪colorFrom: bluecolorTo: greensdk: dockerapp_port: 8008license: mit---Note: app_port must match the port uvicorn listens on inside the container (AB_PORT=8008 from Dockerfile). HF routes HTTPS traffic to that exact port.
Sync code (preferred — HF rejects binary PNGs >LFS-threshold on direct git push, so use API):
from huggingface_hub import upload_folderupload_folder( folder_path=".", repo_id="liovina/ab-test-research-designer", repo_type="space", ignore_patterns=[".git/**", "**/__pycache__/**", "archive/**", "docs/demo/*.png", "*.sqlite3*", "**/node_modules/**"], commit_message="Sync from GitHub main",)Practical notes (learned 2026-06-16):
- The
liovinawrite token is already stored in the OS git credential manager (printf 'protocol=https\nhost=huggingface.co\n\n' | git credential fillreturnsusername=liovina+ token). Pass it astoken=toupload_folder. Note: thehfCLI can report “Not logged in” even when this credential exists — they are separate stores; don’t be misled. - Deploy from a clean snapshot of committed
main, not the working tree (which carries dist/, caches, untracked notes):git archive main | tar -x -C D:/hfdeploythenfolder_path="D:/hfdeploy". Use a native Windows path — Git Bash/tmpand Windows-Python/tmpresolve differently and the upload fails with “is not a directory”. - HF keeps the old container live during
RUNNING_BUILDING, so the page does not change instantly. Detect rollout by polling the live homepage’s JS asset hash for a change (assets/index-<hash>.js) — do NOT wait for a specific local build hash, because HF’s build environment produces a different hash than a localvite build.
Verify after HF finishes APP_STARTING:
curl https://liovina-ab-test-research-designer.hf.space/health# {"status":"ok","service":"AB Test Research Designer API","version":"1.1.0","environment":"local"}Known limits of the HF free tier:
- container filesystem is ephemeral — SQLite data resets on every redeploy or container restart
- 2 vCPU + 16 GB RAM, no GPU
- docs/demo screenshots are referenced from
raw.githubusercontent.comURLs since HF rejects large binaries without Xet storage
Fly.io Demo Deploy (blocked — credit card required)
Section titled “Fly.io Demo Deploy (blocked — credit card required)”Open mode is recommended for a public showcase. This keeps the hosted demo anonymous and matches the default open runtime in the app.
fly apps create <fly-app-name>fly volumes create ab_test_data --region ams --size 1fly deployNotes:
fly.tomlkeepsapp = "ab-test-research-designer"as a placeholder; replace it afterfly apps createor passfly deploy -a <fly-app-name>.- The Fly volume is mounted at
/data, and SQLite is pointed at/data/projects.sqlite3. - Demo seeding is a manual post-deploy step because Fly
release_commandMachines do not mount persistent volumes:
fly ssh console -C "python scripts/seed_demo_workspace.py --idempotent"Fly.io Deploy (Secure)
Section titled “Fly.io Deploy (Secure)”Secure mode is private by default. Once secrets are set, callers must present the configured token; if you share a readonly token, it enables safe GET access but it is no longer an anonymous public demo.
fly secrets set AB_API_TOKEN=... AB_READONLY_API_TOKEN=... AB_WORKSPACE_SIGNING_KEY=...fly deployHealth / Verification
Section titled “Health / Verification”Open runtime:
curl http://127.0.0.1:8008/healthcurl http://127.0.0.1:8008/readyzcurl http://127.0.0.1:8008/api/v1/diagnosticscurl http://127.0.0.1:8008/Expected responses:
GET /health->200with"status":"ok"and"version":"1.1.0".GET /readyz->200with"status":"ready"and all readiness checks markedok.GET /api/v1/diagnostics->200andstorage.write_probe_ok=true.GET /->200and HTML titleAB Test Research Designer.
Secure runtime:
curl -X POST http://127.0.0.1:8008/api/v1/calculate -H "Content-Type: application/json" -d '{"metric_type":"binary","baseline_value":0.1,"mde_pct":5,"alpha":0.05,"power":0.8,"expected_daily_traffic":1000,"audience_share_in_test":1.0,"traffic_split":[50,50],"variants_count":2}'curl -X POST http://127.0.0.1:8008/api/v1/calculate -H "Authorization: Bearer <WRITE_TOKEN>" -H "Content-Type: application/json" -d '{"metric_type":"binary","baseline_value":0.1,"mde_pct":5,"alpha":0.05,"power":0.8,"expected_daily_traffic":1000,"audience_share_in_test":1.0,"traffic_split":[50,50],"variants_count":2}'Expected auth behavior:
- Without a write token,
POST /api/v1/calculatereturns401. - With
Authorization: Bearer <WRITE_TOKEN>,POST /api/v1/calculatereturns200. - In dual-token mode, use
<READONLY_TOKEN>for read-only diagnostics and<WRITE_TOKEN>for mutating endpoints.
Rollback
Section titled “Rollback”Stop the current container, pull or retag the previous release, and run the previous tag again.
docker stop ab-test-v1-opendocker run --rm --name ab-test-v1-rollback -p 8008:8008 <REGISTRY>/ab-test-research-designer:<PREVIOUS_TAG>If the previous image already exists locally, retag it first:
docker tag <REGISTRY>/ab-test-research-designer:<PREVIOUS_TAG> ab-test-research-designer:rollbackdocker run --rm --name ab-test-v1-rollback -p 8008:8008 ab-test-research-designer:rollback